From ee12d74c67b83f1bc3c3d7799573f480a501c880 Mon Sep 17 00:00:00 2001 From: yukirij Date: Sat, 10 Aug 2024 15:04:34 -0700 Subject: [PATCH] Add persistent storage for salt and handle. --- server/Cargo.toml | 1 + server/docs/protocol/requests.md | 36 --- server/docs/protocol/requests.szun | 143 +++++++++ server/src/app/connection.rs | 19 +- server/src/app/mod.rs | 74 +++-- server/src/app/user.rs | 2 +- server/src/main.rs | 7 +- server/src/manager/data.rs | 135 ++++++-- server/src/manager/ws.rs | 163 +++------- server/src/protocol/code.rs | 5 +- server/src/protocol/mod.rs | 4 +- server/src/protocol/packet/connect.rs | 10 +- server/src/protocol/packet/session_list.rs | 2 +- server/src/system/filesystem/mod.rs | 244 ++++++++++++++ server/src/system/mod.rs | 1 + server/src/util/pack.rs | 6 +- www/js/const.js | 15 +- www/js/game.js | 351 ++++++++++++--------- www/js/interface.js | 8 + www/js/main.js | 2 + www/js/scene.js | 85 ++++- www/js/system.js | 2 +- www/js/ui.js | 4 +- 23 files changed, 901 insertions(+), 418 deletions(-) delete mode 100644 server/docs/protocol/requests.md create mode 100644 server/docs/protocol/requests.szun create mode 100644 server/src/system/filesystem/mod.rs diff --git a/server/Cargo.toml b/server/Cargo.toml index 1799fc5..9931c8d 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -19,6 +19,7 @@ http-body-util = "0.1.2" futures = "0.3.30" rust-argon2 = "2.1.0" ring = "0.17.8" +const_format = "0.2.32" game = { path = "../game" } diff --git a/server/docs/protocol/requests.md b/server/docs/protocol/requests.md deleted file mode 100644 index af9830f..0000000 --- a/server/docs/protocol/requests.md +++ /dev/null @@ -1,36 +0,0 @@ -# Table - -## User Authentication - - - Register—create a new account. - - Handle - - Secret - - Invite_Code - - Authenticate—log into an account and create a new client session. - - Handle - - Secret - - Deauthenticate—revoke the current client session. - -## Sessions - - - List_Sessions— - - Page - - Ongoing or Complete - - Joinable - - Live - - Update_Sessions— - -## Game - - - Join— - - Session_Id - - Spectate— - - Session_Id - - Leave— - - Retire— - - Play— - - Move_Source [ Board, Pool ] - - Move_From - - Piece_From - - Move_To - - Piece_To diff --git a/server/docs/protocol/requests.szun b/server/docs/protocol/requests.szun new file mode 100644 index 0000000..a5e4a7c --- /dev/null +++ b/server/docs/protocol/requests.szun @@ -0,0 +1,143 @@ +### Status Codes ## + +# General + +0000 OK +0001 ERROR +0002 NOAUTH +00FE SERVER_ERROR +00FF NOT_IMPLEMENTED + +# Context Specific + +0010 BAD_HANDLE +0011 BAD_SECRET +0012 BAD_CODE + +### + +## Authentication ## + +# 0010 REGISTER +Req_Register { + handle :sequence # username + secret :sequence # password + code :sequence # invitation code +} +Res_Register { + status :block<2> + # OK - registration accepted + # BAD_HANDLE - handle empty or not vacant + # BAD_SECRET - secret is empty + # BAD_CODE - code not accepted + token :block<8> # auth key + secret :block<16> # resume code +} + +# 0011 AUTH +Req_Auth { + handle :sequence # username + secret :sequence # password +} +Res_Auth { + status :block<2> + # STATUS_OK - authentication successful + # ERROR - authentication failed + token :block<8> # auth key + secret :block<16> # resume code +} + +# 0012 AUTH_RESUME +Req_AuthResume { + token :block<8> # auth key + secret :block<16> # resume code +} +Res_AuthResume { + status :block<2> + # OK - resume accepted + # ERROR - auth/code pair not accepted +} + +# 0013 AUTH_REVOKE +Req_AuthRevoke { } +Res_AuthRevoke { } + + +## Session ## + +# 0020 SESSION_LIST +Req_SessionList { + flags :block<2> + # 0[2] Game State + # 2[1] Is Player + # 3[1] Is Live + page :block<2> +} +Res_SessionList { + records :list<{ + token :block<8> # session key + handle_dawn :sequence # username of dawn player + handle_dusk :sequence # username of dusk player + turn :block<2> # turn number + last_move :block<3> # most recent move code + viewers :block<4> # number of viewers + player :bool # user is player + }> +} + +# 0021 SESSION_CREATE +Req_SessionCreate { } +Res_SessionCreate { + status :block<2> + token :block<8> + mode :block<1> + # 0: Player Dawn + # 1: Player Dusk + # 2: Spectator +} + +# 0022 SESSION_JOIN +Res_SessionJoin { + token :block<8> # session key +} +Req_SessionJoin { + status :block<2> + # OK + # NOAUTH + token :block<8> # session key + mode :block<1> + # 0: Player Dawn + # 1: Player Dusk + # 2: Spectator +} + +# 002E SESSION_LEAVE +Req_SessionLeave { } +Res_SessionLeave { } + +# 002F SESSION_RETIRE +Req_SessionRetire { + token :block<8> # session key +} +Res_SessionRetire { + status:block<2> +} + + +## Game ## + +# 0030 GAME_STATE +Req_GameState { + token :block<8> # session token +} +Res_GameState { + +} + +# 0031 GAME_PLAY +Req_GamePlay { + token :block<8> # session token +} +Res_GamePlay { + +} diff --git a/server/src/app/connection.rs b/server/src/app/connection.rs index c130944..452962b 100644 --- a/server/src/app/connection.rs +++ b/server/src/app/connection.rs @@ -1,15 +1,16 @@ +use std::sync::Arc; +use tokio::sync::RwLock; +use futures::stream::SplitSink; +use hyper::upgrade::Upgraded; +use hyper_util::rt::TokioIo; +use tokio_tungstenite::{tungstenite::Message, WebSocketStream}; + use crate::app::authentication::AuthToken; +type StreamType = Arc>, Message>>>; + pub struct Connection { pub bus:u32, + pub stream:StreamType, pub auth:Option, } -impl Connection { - pub fn new() -> Self - { - Self { - bus:0, - auth:None, - } - } -} diff --git a/server/src/app/mod.rs b/server/src/app/mod.rs index 33f39e7..45d15b0 100644 --- a/server/src/app/mod.rs +++ b/server/src/app/mod.rs @@ -3,9 +3,9 @@ use sparse::Sparse; use pool::Pool; use trie::Trie; -use crate::util::{ - Chain, - pack::pack_u32, +use crate::{ + system::filesystem::FileSystem, + util::Chain, }; pub mod connection; use connection::Connection; @@ -15,13 +15,15 @@ pub mod session; use session::{Session, SessionToken}; pub mod context; pub struct App { + pub filesystem:FileSystem, + pub connections:Pool, pub users:Pool, pub user_next:u32, pub user_id:Sparse, pub user_handle:Trie, - pub salts:Vec<[u8; 16]>, + pub salts:Sparse<[u8; 16]>, pub auths:Trie, pub sessions:Trie, @@ -29,39 +31,45 @@ pub struct App { pub session_time:Chain, } impl App { - pub fn new() -> Self + pub fn init() -> Result { - Self { - connections:Pool::new(), + if let Ok(mut filesystem) = FileSystem::init() { - users:Pool::new(), - user_next:0, - user_id:Sparse::new(), - user_handle:Trie::new(), - salts:Vec::new(), + // Load salts + let mut salts = Sparse::new(); + let salt_count = filesystem.salt_count()?; + for id in 0..salt_count { + let salt = filesystem.salt_fetch(id as u32).unwrap(); + salts.set(id as isize, salt); + } - auths:Trie::new(), - sessions:Trie::new(), + // Load handles + let mut user_handle = Trie::new(); + let handle_count = filesystem.handle_count()?; + for id in 0..handle_count { + let (handle, user_id) = filesystem.handle_fetch(id as u32).unwrap(); + println!("got: {} = {}", handle, user_id); + user_handle.set(handle.as_bytes(), user_id); + } - session_time:Chain::new(), + Ok(Self { + filesystem:filesystem, + + connections:Pool::new(), + + users:Pool::new(), + user_next:0, + user_id:Sparse::new(), + user_handle:Trie::new(), + salts, + + auths:Trie::new(), + sessions:Trie::new(), + + session_time:Chain::new(), + }) + } else { + Err(()) } } - - pub fn init(&mut self) -> Result<(),std::io::Error> - { use std::{path::Path, fs}; - - let path_data = Path::new("data"); - if !path_data.exists() { - fs::create_dir(path_data)?; - fs::create_dir(path_data.join("c"))?; - fs::create_dir(path_data.join("s"))?; - fs::create_dir(path_data.join("u"))?; - - fs::write(path_data.join("c/.i"), pack_u32(0)).ok(); - fs::write(path_data.join("s/.i"), pack_u32(0)).ok(); - fs::write(path_data.join("u/.i"), pack_u32(0)).ok(); - } - - Ok(()) - } } diff --git a/server/src/app/user.rs b/server/src/app/user.rs index 328c302..532b6bd 100644 --- a/server/src/app/user.rs +++ b/server/src/app/user.rs @@ -2,5 +2,5 @@ pub struct User { pub id:u32, pub handle:String, pub secret:Vec, - pub na_key:usize, + pub na_key:u32, } diff --git a/server/src/main.rs b/server/src/main.rs index 5959408..188ec10 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -100,13 +100,14 @@ async fn main() }; // Initialize application data. - let mut app = App::new(); - if app.init().is_err() { + let app; + if let Ok(result) = App::init() { + app = result; + } else { println!("fatal: failed to initialize server."); return; } - // Initialize central bus and data serivce. let b_main :Bus = Bus::new_as(bus::Mode::Transmitter); match b_main.connect() { diff --git a/server/src/manager/data.rs b/server/src/manager/data.rs index 8331053..b9a2b44 100644 --- a/server/src/manager/data.rs +++ b/server/src/manager/data.rs @@ -1,3 +1,6 @@ +use tokio_tungstenite::tungstenite::Message; +use futures::SinkExt; + use bus::Bus; use crate::{ app::{ @@ -7,20 +10,28 @@ use crate::{ connection::Connection, }, protocol, + util::pack::pack_u16, }; +fn encode_response(code:u16, data:Vec) -> Vec +{ + [ + pack_u16(code), + data, + ].concat() +} + pub async fn thread_system(mut app:App, bus:Bus) { use protocol::*; - use protocol::QRPacketData::*; use ring::rand::{SecureRandom, SystemRandom}; let rng = SystemRandom::new(); let argon_config = argon2::Config::default(); - while match bus.receive_wait() { + while let Some(response) = match bus.receive_wait() { Some(packet) => { - let qr = &packet.data; + let qr = packet.data; let mut user_id = None; if let Some(conn) = app.connections.get(qr.id as usize) { @@ -31,28 +42,41 @@ pub async fn thread_system(mut app:App, bus:Bus) } } - match &qr.data { - QConn(request) => { + match qr.data { + QRPacketData::QConn(request) => { let id = app.connections.add(Connection { bus: request.bus_id, + stream: request.stream, auth: None, }); println!("Connect: {}", id); - bus.send(packet.from, QRPacket::new(id as u32, RConn)).is_ok() + bus.send( + packet.from, + QRPacket::new(id as u32, QRPacketData::RConn) + ).ok(); + Some(QRPacket::new(0, QRPacketData::None)) } - QDisconn => { - app.connections.remove(qr.id as usize).ok(); + QRPacketData::QDisconn => { + // Close socket and remove connection if valid + // + if if let Some(conn) = app.connections.get_mut(qr.id as usize) { + let mut socket = conn.stream.write().await; + socket.close().await.ok(); + true + } else { false } { + app.connections.remove(qr.id as usize).ok(); - println!("Disconnect: {}", qr.id); - true + println!("Disconnect: {}", qr.id); + } + Some(QRPacket::new(0, QRPacketData::None)) } - QRegister(request) => { + QRPacketData::QRegister(request) => { let mut response = PacketRegisterResponse::new(); - response.status = STATUS_ERROR; + response.status = STATUS_SERVER_ERROR; println!("Request: Register"); @@ -67,8 +91,8 @@ pub async fn thread_system(mut app:App, bus:Bus) let mut salt = [0u8; 16]; match rng.fill(&mut salt) { Ok(_) => { - let salt_id = app.salts.len(); - app.salts.push(salt); + let salt_id = app.filesystem.salt_store(salt).unwrap(); + app.salts.set(salt_id as isize, salt); if let Ok(hash) = argon2::hash_raw(&request.secret, &salt, &argon_config) { let user_id = app.user_next; @@ -83,6 +107,7 @@ pub async fn thread_system(mut app:App, bus:Bus) }); // Register user pool id and handle + app.filesystem.handle_store(&request.handle, user_id).ok(); app.user_id.set(user_id as isize, user_pos); app.user_handle.set(request.handle.as_bytes(), user_id); @@ -120,10 +145,10 @@ pub async fn thread_system(mut app:App, bus:Bus) } } - bus.send(packet.from, QRPacket::new(qr.id, RRegister(response))).is_ok() + Some(QRPacket::new(qr.id, QRPacketData::RRegister(response))) } - QAuth(request) => { + QRPacketData::QAuth(request) => { let mut response = PacketAuthResponse::new(); response.status = STATUS_ERROR; @@ -141,7 +166,7 @@ pub async fn thread_system(mut app:App, bus:Bus) if let Some(tuid) = app.user_id.get(*uid as isize) { if let Some(user) = app.users.get(*tuid) { // Get user salt - if let Some(salt) = app.salts.get(user.na_key) { + if let Some(salt) = app.salts.get(user.na_key as isize) { // Verify salted secret against user data if argon2::verify_raw(&request.secret.as_bytes(), salt, &user.secret, &argon_config).unwrap_or(false) { @@ -184,10 +209,10 @@ pub async fn thread_system(mut app:App, bus:Bus) } } - bus.send(packet.from, QRPacket::new(qr.id, RAuth(response))).is_ok() + Some(QRPacket::new(qr.id, QRPacketData::RAuth(response))) } - QAuthResume(request) => { + QRPacketData::QAuthResume(request) => { let mut response = PacketAuthResumeResponse::new(); response.status = STATUS_ERROR; @@ -209,10 +234,10 @@ pub async fn thread_system(mut app:App, bus:Bus) } } - bus.send(packet.from, QRPacket::new(qr.id, RAuthResume(response))).is_ok() + Some(QRPacket::new(qr.id, QRPacketData::RAuthResume(response))) } - QDeauth => { + QRPacketData::QAuthRevoke => { if let Some(conn) = app.connections.get_mut(qr.id as usize) { match conn.auth { Some(auth) => { @@ -223,10 +248,10 @@ pub async fn thread_system(mut app:App, bus:Bus) } conn.auth = None; } - true + Some(QRPacket::new(0, QRPacketData::None)) } - QSessionList(request) => { + QRPacketData::QSessionList(request) => { use game::game::GameState; println!("Request: Session List"); @@ -277,7 +302,7 @@ pub async fn thread_system(mut app:App, bus:Bus) dusk_handle, ], turn:0, - last_move:[0; 5], + last_move:[0; 3], viewers:0, player:is_player, }); @@ -290,10 +315,10 @@ pub async fn thread_system(mut app:App, bus:Bus) next_id = app.session_time.next(id); } - bus.send(packet.from, QRPacket::new(qr.id, RSessionList(response))).is_ok() + Some(QRPacket::new(qr.id, QRPacketData::RSessionList(response))) } - QSessionCreate(_request) => { + QRPacketData::QSessionCreate(_request) => { use crate::app::session::*; println!("Request: Session Create"); @@ -336,10 +361,10 @@ pub async fn thread_system(mut app:App, bus:Bus) response.token = token; } - bus.send(packet.from, QRPacket::new(qr.id, RSessionCreate(response))).is_ok() + Some(QRPacket::new(qr.id, QRPacketData::RSessionCreate(response))) } - QSessionJoin(request) => { + QRPacketData::QSessionJoin(request) => { println!("Request: Session Join"); let mut response = PacketSessionJoinResponse::new(); @@ -390,12 +415,58 @@ pub async fn thread_system(mut app:App, bus:Bus) } } - bus.send(packet.from, QRPacket::new(qr.id, RSessionJoin(response))).is_ok() + Some(QRPacket::new(qr.id, QRPacketData::RSessionJoin(response))) } - _ => { true } + _ => { Some(QRPacket::new(0, QRPacketData::None)) } } } - None => false, - } { } + None => None, + } { + if match response.data { QRPacketData::None => false, _ => true } { + if let Some(conn) = app.connections.get_mut(response.id as usize) { + let mut socket = conn.stream.write().await; + + match response.data { + QRPacketData::RRegister(response) => { + socket.send(Message::Binary( + encode_response(CODE_REGISTER, response.encode()) + )).await.ok(); + } + + QRPacketData::RAuth(response) => { + socket.send(Message::Binary( + encode_response(CODE_AUTH, response.encode()) + )).await.ok(); + } + + QRPacketData::RAuthResume(response) => { + socket.send(Message::Binary( + encode_response(CODE_AUTH_RESUME, response.encode()) + )).await.ok(); + } + + QRPacketData::RSessionList(response) => { + socket.send(Message::Binary( + encode_response(CODE_SESSION_LIST, response.encode()) + )).await.ok(); + } + + QRPacketData::RSessionCreate(response) => { + socket.send(Message::Binary( + encode_response(CODE_SESSION_CREATE, response.encode()) + )).await.ok(); + } + + QRPacketData::RSessionJoin(response) => { + socket.send(Message::Binary( + encode_response(CODE_SESSION_JOIN, response.encode()) + )).await.ok(); + } + + _ => { } + } + } + } + } } diff --git a/server/src/manager/ws.rs b/server/src/manager/ws.rs index 612ef4f..54ebff9 100644 --- a/server/src/manager/ws.rs +++ b/server/src/manager/ws.rs @@ -1,45 +1,42 @@ +use std::sync::Arc; +use tokio::sync::RwLock; use hyper::upgrade::Upgraded; use hyper_util::rt::TokioIo; use tokio_tungstenite::WebSocketStream; -use futures::{SinkExt, StreamExt}; +use futures::StreamExt; use crate::{ protocol, - util::pack::{pack_u16, unpack_u16}, + util::pack::unpack_u16, HttpServiceArgs, }; -fn encode_response(code:u16, data:Vec) -> Vec -{ - [ - pack_u16(code), - data, - ].concat() -} - -pub async fn handle_ws(mut ws:WebSocketStream>, args:HttpServiceArgs) -> Result<(),()> +pub async fn handle_ws(ws:WebSocketStream>, args:HttpServiceArgs) -> Result<(),()> // Handle websocket connection. // { use tokio_tungstenite::tungstenite::protocol::Message; - use protocol::{QRPacket, QRPacketData::*, code::*, packet::*}; + use protocol::{QRPacket, QRPacketData, code::*, packet::*}; let conn_id :u32; + let (sink, mut stream) = ws.split(); + let bus_ds = args.bus.mailbox(1).unwrap_or(1); // Perform connection handshake with data system. // - Provide system with connection/bus pairing. // - Acquire connection id. // - args.bus.send(bus_ds, QRPacket::new(0, QConn(LocalPacketConnect { + args.bus.send(bus_ds, QRPacket::new(0, QRPacketData::QConn(LocalPacketConnect { bus_id:args.bus.id(), + stream:Arc::new(RwLock::new(sink)), })))?; match args.bus.receive_wait() { Some(resp) => { let qr = &resp.data; match qr.data { - RConn => { conn_id = qr.id; } + QRPacketData::RConn => { conn_id = qr.id; } _ => { return Err(()); } } } @@ -49,7 +46,7 @@ pub async fn handle_ws(mut ws:WebSocketStream>, args:HttpServi // Decode client requests from websocket, // pass requests to data system, // and return responses to client. - while match ws.next().await { + while match stream.next().await { Some(msg) => match msg { Ok(msg) => match msg { Message::Binary(data) => { @@ -62,142 +59,67 @@ pub async fn handle_ws(mut ws:WebSocketStream>, args:HttpServi CODE_REGISTER => match PacketRegister::decode(&data, &mut index) { Ok(packet) => { - if args.bus.send(bus_ds, QRPacket::new(conn_id, QRegister(packet))).is_ok() { - while match args.bus.receive_wait() { - Some(resp) => { - let qr = &resp.data; - match &qr.data { - RRegister(resp) => { - ws.send(Message::Binary( - encode_response(code, resp.encode()) - )).await.ok(); - false - } - _ => true, - } - } - None => true, - } { } - } + args.bus.send( + bus_ds, + QRPacket::new(conn_id, QRPacketData::QRegister(packet)) + ).ok(); } Err(_) => { } } CODE_AUTH => match PacketAuth::decode(&data, &mut index) { Ok(packet) => { - if args.bus.send(bus_ds, QRPacket::new(conn_id, QAuth(packet))).is_ok() { - while match args.bus.receive_wait() { - Some(resp) => { - let qr = &resp.data; - match &qr.data { - RAuth(resp) => { - ws.send(Message::Binary( - encode_response(code, resp.encode()) - )).await.ok(); - false - } - _ => true, - } - } - None => true, - } { } - } + args.bus.send( + bus_ds, + QRPacket::new(conn_id, QRPacketData::QAuth(packet)) + ).ok(); } Err(_) => { } } CODE_AUTH_RESUME => match PacketAuthResume::decode(&data, &mut index) { Ok(packet) => { - if args.bus.send(bus_ds, QRPacket::new(conn_id, QAuthResume(packet))).is_ok() { - while match args.bus.receive_wait() { - Some(resp) => { - let qr = &resp.data; - match &qr.data { - RAuth(resp) => { - ws.send(Message::Binary( - encode_response(code, resp.encode()) - )).await.ok(); - false - } - _ => true, - } - } - None => true, - } { } - } + args.bus.send( + bus_ds, + QRPacket::new(conn_id, QRPacketData::QAuthResume(packet)) + ).ok(); } Err(_) => { } } - CODE_DEAUTH => { - args.bus.send(bus_ds, QRPacket::new(conn_id, QDeauth)).ok(); + CODE_AUTH_REVOKE => { + args.bus.send( + bus_ds, QRPacket::new(conn_id, + QRPacketData::QAuthRevoke) + ).ok(); } CODE_SESSION_LIST => match PacketSessionList::decode(&data, &mut index) { Ok(packet) => { - if args.bus.send(bus_ds, QRPacket::new(conn_id, QSessionList(packet))).is_ok() { - while match args.bus.receive_wait() { - Some(resp) => { - let qr = &resp.data; - match &qr.data { - RSessionList(resp) => { - ws.send(Message::Binary( - encode_response(code, resp.encode()) - )).await.ok(); - false - } - _ => true, - } - } - None => true, - } { } - } + args.bus.send( + bus_ds, + QRPacket::new(conn_id, QRPacketData::QSessionList(packet)) + ).ok(); } Err(_) => { println!("error: packet decode failed."); } } CODE_SESSION_CREATE => match PacketSessionCreate::decode(&data, &mut index) { Ok(packet) => { - if args.bus.send(bus_ds, QRPacket::new(conn_id, QSessionCreate(packet))).is_ok() { - while match args.bus.receive_wait() { - Some(resp) => { - let qr = &resp.data; - match &qr.data { - RSessionCreate(resp) => { - ws.send(Message::Binary( - encode_response(code, resp.encode()) - )).await.ok(); - false - } - _ => true, - } - } - None => true, - } { } - } + args.bus.send( + bus_ds, + QRPacket::new(conn_id, QRPacketData::QSessionCreate(packet)) + ).ok(); } Err(_) => { println!("error: packet decode failed."); } } CODE_SESSION_JOIN => match PacketSessionJoin::decode(&data, &mut index) { Ok(packet) => { - if args.bus.send(bus_ds, QRPacket::new(conn_id, QSessionJoin(packet))).is_ok() { - while match args.bus.receive_wait() { - Some(resp) => { - let qr = &resp.data; - match &qr.data { - RSessionJoin(resp) => { - ws.send(Message::Binary( - encode_response(code, resp.encode()) - )).await.ok(); - false - } - _ => true, - } - } - None => true, - } { } - } + args.bus.send( + bus_ds, + QRPacket::new(conn_id, QRPacketData::QSessionJoin(packet)) + ).ok(); } Err(_) => { println!("error: packet decode failed."); } } @@ -216,7 +138,6 @@ pub async fn handle_ws(mut ws:WebSocketStream>, args:HttpServi None => false, } { } - args.bus.send(bus_ds, QRPacket::new(conn_id, QDisconn)).ok(); - ws.close(None).await.ok(); + args.bus.send(bus_ds, QRPacket::new(conn_id, QRPacketData::QDisconn)).ok(); Ok(()) } diff --git a/server/src/protocol/code.rs b/server/src/protocol/code.rs index 2225af6..49c1392 100644 --- a/server/src/protocol/code.rs +++ b/server/src/protocol/code.rs @@ -20,8 +20,8 @@ pub const STATUS_NOT_IMPL :u16 = 0x00FF; pub const CODE_REGISTER :u16 = 0x0010; pub const CODE_AUTH :u16 = 0x0011; -pub const CODE_DEAUTH :u16 = 0x0013; pub const CODE_AUTH_RESUME :u16 = 0x0012; +pub const CODE_AUTH_REVOKE :u16 = 0x0013; pub const CODE_SESSION_LIST :u16 = 0x0020; pub const CODE_SESSION_CREATE :u16 = 0x0021; @@ -29,4 +29,5 @@ pub const CODE_SESSION_JOIN :u16 = 0x0022; pub const CODE_SESSION_RETIRE :u16 = 0x002E; pub const CODE_SESSION_LEAVE :u16 = 0x002F; -pub const CODE_GAME_PLAY :u16 = 0x0030; +pub const CODE_GAME_STATE :u16 = 0x0030; +pub const CODE_GAME_PLAY :u16 = 0x0031; diff --git a/server/src/protocol/mod.rs b/server/src/protocol/mod.rs index 157d279..66dec2d 100644 --- a/server/src/protocol/mod.rs +++ b/server/src/protocol/mod.rs @@ -5,7 +5,7 @@ pub mod packet; pub use packet::*; #[derive(Clone)] pub enum QRPacketData { - ROk, + None, QConn(LocalPacketConnect), RConn, @@ -21,7 +21,7 @@ pub enum QRPacketData { QAuthResume(PacketAuthResume), RAuthResume(PacketAuthResumeResponse), - QDeauth, + QAuthRevoke, QSessionList(PacketSessionList), RSessionList(PacketSessionListResponse), diff --git a/server/src/protocol/packet/connect.rs b/server/src/protocol/packet/connect.rs index c1ea047..299c597 100644 --- a/server/src/protocol/packet/connect.rs +++ b/server/src/protocol/packet/connect.rs @@ -1,4 +1,12 @@ -#[derive(Clone, Copy)] +use std::sync::Arc; +use tokio::sync::RwLock; +use futures::stream::SplitSink; +use hyper::upgrade::Upgraded; +use hyper_util::rt::TokioIo; +use tokio_tungstenite::{tungstenite::Message, WebSocketStream}; + +#[derive(Clone)] pub struct LocalPacketConnect { pub bus_id:u32, + pub stream:Arc>, Message>>>, } diff --git a/server/src/protocol/packet/session_list.rs b/server/src/protocol/packet/session_list.rs index d66ca3c..6d842b5 100644 --- a/server/src/protocol/packet/session_list.rs +++ b/server/src/protocol/packet/session_list.rs @@ -65,7 +65,7 @@ pub struct PacketSessionListResponseRecord { pub token:SessionToken, pub handles:[String; 2], pub turn:u16, - pub last_move:[u8; 5], + pub last_move:[u8; 3], pub viewers:u32, pub player:bool, } diff --git a/server/src/system/filesystem/mod.rs b/server/src/system/filesystem/mod.rs new file mode 100644 index 0000000..ccedabd --- /dev/null +++ b/server/src/system/filesystem/mod.rs @@ -0,0 +1,244 @@ +use std::{ + fs::{self, File}, io::{Read, Seek, SeekFrom, Write}, path::Path +}; + +use crate::{ + app::session::Session, + app::user::User, + util::pack::{pack_u32, unpack_u32} +}; + +const HANDLE_BUCKET_MASK :u32 = 0xFF; +const HANDLE_BUCKET_SIZE :u32 = HANDLE_BUCKET_MASK + 1; + +const DIR_DATA :&str = "data"; +const DIR_HANDLE :&str = const_format::formatcp!("{}/h", DIR_DATA); +const DIR_SESSION :&str = const_format::formatcp!("{}/s", DIR_DATA); +const DIR_USER :&str = const_format::formatcp!("{}/u", DIR_DATA); + +const INDEX_HANDLE :&str = const_format::formatcp!("{}/i.bin", DIR_HANDLE); +const INDEX_SESSION :&str = const_format::formatcp!("{}/i.bin", DIR_SESSION); +const INDEX_USER :&str = const_format::formatcp!("{}/i.bin", DIR_USER); + +pub const FILE_SALT :&str = const_format::formatcp!("{}/x.bin", DIR_DATA); + +pub struct FileSystem { + index_handle:File, + index_session:File, + index_user:File, + + table_salt:File, +} +impl FileSystem { + pub fn init() -> Result + { + // + // TEMPORARY: REMOVE AFTER TESTING + // + //fs::remove_dir_all(DIR_DATA).ok(); + + // Initialize filesystem if does not exist. + // + // Note: does not currently check for corruption. + // + if !Path::new(DIR_DATA).exists() { + fs::create_dir(DIR_DATA)?; + fs::create_dir(DIR_HANDLE)?; + fs::create_dir(DIR_SESSION)?; + fs::create_dir(DIR_USER)?; + + fs::write(INDEX_HANDLE, pack_u32(0))?; + fs::write(INDEX_SESSION, pack_u32(0))?; + fs::write(INDEX_USER, pack_u32(0))?; + + fs::write(FILE_SALT, pack_u32(0))?; + } + + let index_handle = File::options() + .read(true) + .write(true) + .open(INDEX_HANDLE)?; + let index_session = File::options() + .read(true) + .write(true) + .open(INDEX_SESSION)?; + let index_user = File::options() + .read(true) + .write(true) + .open(INDEX_USER)?; + + let table_salt = File::options() + .read(true) + .write(true) + .open(FILE_SALT)?; + + Ok(Self { + index_handle, + index_session, + index_user, + + table_salt, + }) + } + + + pub fn session_store(&mut self, _session:&Session) -> Result<(),()> + { + Err(()) + } + + + pub fn user_store(&mut self, _user:&User) -> Result<(),()> + { + Err(()) + } + + + pub fn handle_store(&mut self, handle:&String, user_id:u32) -> Result + // Add a salt to store. + // + { + let size = self.handle_count()? as u32; + let data = handle.as_bytes(); + let length = data.len(); + + let bucket_index = size & !HANDLE_BUCKET_MASK; + let record_index = size & HANDLE_BUCKET_MASK; + let record_offset = 12 * record_index as u64; + + // Update size record + self.index_handle.seek(SeekFrom::Start(0)).map_err(|_| ())?; + self.index_handle.write(&pack_u32(size + 1)).map_err(|_| ())?; + + // Create bucket file if not exists + let bucket_path = Path::new(DIR_HANDLE).join(format!("{:x}.bin", bucket_index)); + if !bucket_path.exists() { + fs::write(bucket_path.clone(), vec![0u8; 12 * HANDLE_BUCKET_SIZE as usize]).map_err(|_| ())?; + } + + // Open bucket file for record + if let Ok(mut file) = File::options().read(true).write(true).open(bucket_path) { + let offset = file.seek(SeekFrom::End(0)).map_err(|_| ())?; + + // Write record header to table + let buffer_header = [ + pack_u32(offset as u32), + pack_u32(length as u32), + pack_u32(user_id), + ].concat(); + + file.seek(SeekFrom::Start(record_offset)).map_err(|_| ())?; + file.write(&buffer_header).map_err(|_| ())?; + + + // Write handle data to end of file + file.seek(SeekFrom::End(0)).map_err(|_| ())?; + file.write(data).map_err(|_| ())?; + + Ok(size) + } else { Err(()) } + } + + pub fn handle_fetch(&mut self, id:u32) -> Result<(String, u32),()> + // Retrieve a salt from store. + // + { + let size = self.salt_count()? as u32; + let mut buffer = Vec::new(); + + // Get location of handle by index + if id < size { + let bucket_index = id & !HANDLE_BUCKET_MASK; + let record_index = id & HANDLE_BUCKET_MASK; + let record_offset = 12 * record_index as u64; + + let path = Path::new(DIR_HANDLE) + .join(format!("{:x}.bin", bucket_index)); + + // Read bucket file for record + if let Ok(mut file) = File::open(path) { + + // Get record offset, length, and user association + let mut buffer_header = [0u8; 12]; + file.seek(SeekFrom::Start(record_offset)).map_err(|_| ())?; + if file.read_exact(&mut buffer_header).is_ok() { + let offset = unpack_u32(&buffer_header, &mut 0); + let length = unpack_u32(&buffer_header, &mut 4); + let user_id = unpack_u32(&buffer_header, &mut 8); + + if offset != 0 { + buffer.resize(length as usize, 0); + + // Read handle data from offset + file.seek(SeekFrom::Start(offset as u64)).map_err(|_| ())?; + match file.read_exact(&mut buffer) { + Ok(_) => { + Ok((String::from_utf8(buffer).map_err(|_| ())?, user_id)) + } + Err(e) => { println!("e: {}", e.to_string()); Err(()) } + } + } else { Err(()) } + } else { Err(()) } + } else { Err(()) } + } else { Err(()) } + } + + pub fn handle_count(&mut self) -> Result + // Get number of salts in store. + // + { + Self::get_header_size(&mut self.index_handle) + } + + + pub fn salt_store(&mut self, salt:[u8; 16]) -> Result + // Add a salt to store. + // + { + let size = self.salt_count()? as u32; + + // Update size record + self.table_salt.seek(SeekFrom::Start(0)).map_err(|_| ())?; + self.table_salt.write(&pack_u32(size + 1)).map_err(|_| ())?; + + // Write salt to store + self.table_salt.seek(SeekFrom::End(0)).map_err(|_| ())?; + self.table_salt.write(&salt).map_err(|_| ())?; + + Ok(size) + } + + pub fn salt_fetch(&mut self, id:u32) -> Result<[u8; 16],()> + // Retrieve a salt from store. + // + { + let size = self.salt_count()? as u32; + let mut buffer = [0u8; 16]; + + if id < size { + let offset = 4 + (16 * id as u64); + + self.table_salt.seek(SeekFrom::Start(offset)).map_err(|_| ())?; + if self.table_salt.read_exact(&mut buffer).is_ok() { + Ok(buffer) + } else { Err(()) } + } else { Err(()) } + } + + pub fn salt_count(&mut self) -> Result + // Get number of salts in store. + // + { + Self::get_header_size(&mut self.table_salt) + } + + fn get_header_size(file:&mut File) -> Result + { + let mut size_buffer = [0u8; 4]; + + file.seek(SeekFrom::Start(0)).map_err(|_| ())?; + if file.read_exact(&mut size_buffer).is_ok() { + Ok(unpack_u32(&size_buffer, &mut 0) as usize) + } else { Err(()) } + } +} diff --git a/server/src/system/mod.rs b/server/src/system/mod.rs index 9057889..717244e 100644 --- a/server/src/system/mod.rs +++ b/server/src/system/mod.rs @@ -2,3 +2,4 @@ pub mod net; pub mod cache; +pub mod filesystem; diff --git a/server/src/util/pack.rs b/server/src/util/pack.rs index 1928590..bbed1be 100644 --- a/server/src/util/pack.rs +++ b/server/src/util/pack.rs @@ -3,7 +3,7 @@ pub fn pack_u8(value:u8) -> Vec vec![value] } -pub fn unpack_u8(data:&Vec, index:&mut usize) -> u8 +pub fn unpack_u8(data:&[u8], index:&mut usize) -> u8 { let mut result :u8 = 0; if *index < data.len() { @@ -18,7 +18,7 @@ pub fn pack_u16(value:u16) -> Vec vec![(value >> 8) as u8, (value & 0xFF) as u8] } -pub fn unpack_u16(data:&Vec, index:&mut usize) -> u16 +pub fn unpack_u16(data:&[u8], index:&mut usize) -> u16 { let mut result :u16 = 0; if *index < data.len() { @@ -42,7 +42,7 @@ pub fn pack_u32(value:u32) -> Vec ] } -pub fn unpack_u32(data:&Vec, index:&mut usize) -> u32 +pub fn unpack_u32(data:&[u8], index:&mut usize) -> u32 { let mut result :u32 = 0; if *index < data.len() { diff --git a/www/js/const.js b/www/js/const.js index aebf944..015f53c 100644 --- a/www/js/const.js +++ b/www/js/const.js @@ -11,13 +11,13 @@ let CONTEXT = { }; const Status = { - Ok: 0, - Error: 1, - NotImplement: 2, + Ok: 0x0000, + Error: 0x0001, + NotImplemented: 0x0002, - BadHandle: 1, - BadSecret: 2, - BadCode: 3, + BadHandle: 0x0010, + BadSecret: 0x0011, + BadCode: 0x0012, }; const OpCode = { @@ -29,8 +29,11 @@ const OpCode = { SessionList :0x0020, SessionCreate :0x0021, SessionJoin :0x0022, + SessionRetire :0x002E, + SessionLeave :0x002F, GameState :0x0030, + GamePlay :0x0031, }; const GameState = { diff --git a/www/js/game.js b/www/js/game.js index f71a493..a0c28a0 100644 --- a/www/js/game.js +++ b/www/js/game.js @@ -1,157 +1,212 @@ -class GamePieceMove { +const GAME_CONST = { + PLAYER_DAWN: 0, + PLAYER_DUSK: 1, -} - -class GamePiece { - constructor(name, assets, moves, promote_moves) { - this.name = name; - this.assets = assets; - this.moves = moves; - this.pmoves = promote_moves; - } -} - -let GAME_DATA = { - board: { - tiles: [ ], - }, - - pieces: [ - new PieceDef("Militia「兵」", "♟︎", // ♟︎士 - ["asset/militia_dusk.svg", "asset/militia_dawn.svg"], - new Move() - .add(0) - .add(1) - .add(5), - - new Move() - .add(0) - .add(1) - .add(2) - .add(4) - .add(5) - ), - new PieceDef("Knight「騎」", "♞", // ♞馬 - ["asset/knight_dusk.svg", "asset/knight_dawn.svg"], - new Move() - .add(3) - .add(6) - .add(11) - .add(13) - .add(17), - - new Move() - .add(3) - .add(6) - .add(7) - .add(10) - .add(11) - .add(13) - .add(14) - .add(16) - .add(17) - ), - new PieceDef("Lance「槍」", "♛", // ♛槍 - ["asset/lance_dusk.svg", "asset/lance_dawn.svg"], - new Move() - .add(0, true) - .add(1) - .add(5), - - new Move() - .add(0, true) - .add(1, true) - .add(2, true) - .add(3, true) - .add(4, true) - .add(5, true) - ), - new PieceDef("Tower「楼」", "♖", // ♖高 - ["asset/tower_dusk.svg", "asset/tower_dawn.svg"], - new Move() - .add(0) - .add(1) - .add(3) - .add(5) - .add(6) - .add(11), - - new Move() - .add(0) - .add(1) - .add(2) - .add(3) - .add(4) - .add(5) - .add(6) - .add(8) - .add(9) - .add(11) - ), - new PieceDef("Castle「城」", "♜", // ♜城 - ["asset/castle_dusk.svg", "asset/castle_dawn.svg"], - new Move() - .add(0) - .add(1) - .add(2) - .add(4) - .add(5) - .add(7) - .add(10), - - new Move() - .add(0) - .add(1) - .add(2) - .add(3) - .add(4) - .add(5) - .add(7, true) - .add(10, true) - ), - new PieceDef("Dragon「竜」", "♝", // ♝竜 - ["asset/dragon_dusk.svg", "asset/dragon_dawn.svg"], - new Move() - .add(6, true) - .add(7, true) - .add(8, true) - .add(9, true) - .add(10, true) - .add(11, true), - - new Move() - .add(0, true) - .add(1, true) - .add(2, true) - .add(3, true) - .add(4, true) - .add(5, true) - .add(6, true) - .add(7, true) - .add(8, true) - .add(9, true) - .add(10, true) - .add(11, true) - ), - new PieceDef("King「王」", "♚", // ♚王 - ["asset/king_dusk.svg", "asset/king_dawn.svg"], - new Move() - .add(0) - .add(1) - .add(2) - .add(3) - .add(4) - .add(5) - .add(7) - .add(10) - ), - ], + SOURCE_BOARD: 0, + SOURCE_POOL: 1, }; +const GAME_CLASS = { + Board: class { + constructor() { + this.tiles = [ ]; for(let i = 0; i < 61; ++i) { this.tiles.push(new GAME_CLASS.Tile()); } + this.dawn = new GAME_CLASS.Player(); + } + }, + Player: class { + constructor() { + this.handle = ""; + this.pool = new GAME_CLASS.Pool(); + } + }, + + Pool: class { + constructor() { + this.pieces = [ ]; for(let i = 0; i < 6; ++i) { this.pieces.push(0); } + } + }, + + Tile: class { + constructor() { + this.piece = 0; + } + }, + + Move: class { + constructor(source, from, to) { + this.source = source; + this.from = from; + this.to = to; + } + }, + + GamePiece: class { + constructor(name, assets, moves, promote_moves) { + this.name = name; + this.assets = assets; + this.moves = moves; + this.pmoves = promote_moves; + } + }, + + Piece: class { + constructor(piece, player) { + this.piece = piece; + this.player = player; + this.promoted = false; + } + }, + + Game: class { + constructor() { + this.board = new GAME_CLASS.Board(); + this.pieces = [ + new GAME_CLASS.GamePiece("Militia", + ["asset/militia_dusk.svg", "asset/militia_dawn.svg"], + new Move() + .add(0) + .add(1) + .add(5), + + new Move() + .add(0) + .add(1) + .add(2) + .add(4) + .add(5) + ), + new GAME_CLASS.GamePiece("Knight", + ["asset/knight_dusk.svg", "asset/knight_dawn.svg"], + new Move() + .add(3) + .add(6) + .add(11) + .add(13) + .add(17), + + new Move() + .add(3) + .add(6) + .add(7) + .add(10) + .add(11) + .add(13) + .add(14) + .add(16) + .add(17) + ), + new GAME_CLASS.GamePiece("Lance", + ["asset/lance_dusk.svg", "asset/lance_dawn.svg"], + new Move() + .add(0, true) + .add(1) + .add(5), + + new Move() + .add(0, true) + .add(1, true) + .add(2, true) + .add(3, true) + .add(4, true) + .add(5, true) + ), + new GAME_CLASS.GamePiece("Tower", + ["asset/tower_dusk.svg", "asset/tower_dawn.svg"], + new Move() + .add(0) + .add(1) + .add(3) + .add(5) + .add(6) + .add(11), + + new Move() + .add(0) + .add(1) + .add(2) + .add(3) + .add(4) + .add(5) + .add(6) + .add(8) + .add(9) + .add(11) + ), + new GAME_CLASS.GamePiece("Castle", + ["asset/castle_dusk.svg", "asset/castle_dawn.svg"], + new Move() + .add(0) + .add(1) + .add(2) + .add(4) + .add(5) + .add(7) + .add(10), + + new Move() + .add(0) + .add(1) + .add(2) + .add(3) + .add(4) + .add(5) + .add(7, true) + .add(10, true) + ), + new GAME_CLASS.GamePiece("Dragon", + ["asset/dragon_dusk.svg", "asset/dragon_dawn.svg"], + new Move() + .add(6, true) + .add(7, true) + .add(8, true) + .add(9, true) + .add(10, true) + .add(11, true), + + new Move() + .add(0, true) + .add(1, true) + .add(2, true) + .add(3, true) + .add(4, true) + .add(5, true) + .add(6, true) + .add(7, true) + .add(8, true) + .add(9, true) + .add(10, true) + .add(11, true) + ), + new GAME_CLASS.GamePiece("Omen", + ["asset/king_dusk.svg", "asset/king_dawn.svg"], + new Move() + .add(0) + .add(1) + .add(2) + .add(3) + .add(4) + .add(5) + .add(7) + .add(10) + ), + ]; + } + } +}; + +let GAME_DATA = null; const GAME = { init() { - GAME_DATA.board.tiles + GAME_DATA = new GAME_CLASS.Game(); + }, + + process(move) { + + }, + + validate(move) { + }, }; diff --git a/www/js/interface.js b/www/js/interface.js index 627d98f..cfe416d 100644 --- a/www/js/interface.js +++ b/www/js/interface.js @@ -53,7 +53,11 @@ const INTERFACE = { }, init() { + GAME.init(); + INTERFACE_DATA.canvas = document.getElementById("game"); + let canvas = INTERFACE_DATA.canvas; + if(canvas !== undefined) { INTERFACE_DATA.context = canvas.getContext("2d"); @@ -68,4 +72,8 @@ const INTERFACE = { INTERFACE_DATA.canvas = null; INTERFACE_DATA.context = null; }, + + message(data) { + + }, }; diff --git a/www/js/main.js b/www/js/main.js index f7009bb..4248daa 100644 --- a/www/js/main.js +++ b/www/js/main.js @@ -1,4 +1,6 @@ document.addEventListener("DOMContentLoaded", () => { SCENE = SCENES.Offline; LOAD(SCENES.Init); + + document.addEventListener("beforeunload", UNLOAD); }); diff --git a/www/js/scene.js b/www/js/scene.js index ff05f9a..fcfe59c 100644 --- a/www/js/scene.js +++ b/www/js/scene.js @@ -1,7 +1,7 @@ const SCENES = { Init:{ load() { - LOAD_OFFLINE(); + LOAD_STACK(SCENES.Offline); CONTEXT.Scene = SCENES.Online; RECONNECT(); return true; @@ -210,11 +210,13 @@ const SCENES = { MESSAGE_SESSION_LIST(0, 0, false, false); }, message(code, data) { - let table = document.getElementById("content"); - UI.clear(table); + if(code == OpCode.SessionList) { + let table = document.getElementById("content"); + UI.clear(table); - if(data !== null) { - table.appendChild(UI.session_table(data.records)); + if(data !== null) { + table.appendChild(UI.session_table(data.records)); + } } } }, @@ -250,6 +252,16 @@ const SCENES = { refresh() { }, + message(code, data) { + if(code == OpCode.SessionList) { + let table = document.getElementById("content"); + UI.clear(table); + + if(data !== null) { + table.appendChild(UI.session_table(data.records)); + } + } + } }, Join:{ @@ -283,6 +295,16 @@ const SCENES = { refresh() { }, + message(code, data) { + if(code == OpCode.SessionList) { + let table = document.getElementById("content"); + UI.clear(table); + + if(data !== null) { + table.appendChild(UI.session_table(data.records)); + } + } + } }, Live:{ @@ -315,6 +337,16 @@ const SCENES = { refresh() { }, + message(code, data) { + if(code == OpCode.SessionList) { + let table = document.getElementById("content"); + UI.clear(table); + + if(data !== null) { + table.appendChild(UI.session_table(data.records)); + } + } + } }, History:{ @@ -342,12 +374,25 @@ const SCENES = { refresh() { }, + message(code, data) { + if(code == OpCode.SessionList) { + let table = document.getElementById("content"); + UI.clear(table); + + if(data !== null) { + table.appendChild(UI.session_table(data.records)); + } + } + } }, Guide:{ load() { UI.mainmenu(); UI.mainnav([], []); + + + return true; }, refresh() { @@ -359,6 +404,9 @@ const SCENES = { load() { UI.mainmenu(); UI.mainnav([], []); + + + return true; }, refresh() { @@ -377,7 +425,6 @@ const SCENES = { let canvas = document.createElement("canvas"); canvas.setAttribute("id", "game"); - MAIN.appendChild(canvas); INTERFACE.init(); @@ -385,29 +432,33 @@ const SCENES = { return true; }, unload() { - + MESSAGE_COMPOSE([ + PACK.u16(OpCode.SessionLeave), + ]); }, - refresh() { - - }, - message() { - + message(code, data) { + if(code == OpCode.GameState || code == OpCode.GamePlay) { + INTERFACE.message(code, data); + } }, }, }; function LOAD(scene) { - if(SCENE.unload !== undefined) { SCENE.unload(); } + UNLOAD(); UI.rebuild(); SCENE = scene; CONTEXT.Scene = SCENE; if(!SCENE.load()) { LOAD(SCENES.Online); } } -function LOAD_OFFLINE() { - if(SCENE.unload !== undefined) { SCENE.unload(); } - while(document.body.lastChild !== null) { document.body.removeChild(document.body.lastChild); } +function UNLOAD() { + if(SCENE !== null && SCENE.unload !== undefined) { SCENE.unload(); } +} + +function LOAD_STACK(scene) { + UNLOAD(); UI.rebuild(); - SCENE = SCENES.Offline; + SCENE = scene; if(!SCENE.load()) { LOAD(SCENES.Online); } } diff --git a/www/js/system.js b/www/js/system.js index f0e0f86..bc739ef 100644 --- a/www/js/system.js +++ b/www/js/system.js @@ -5,7 +5,7 @@ function RECONNECT() { SOCKET.binaryType = "arraybuffer"; SOCKET.addEventListener("error", (event) => { SOCKET = null; - LOAD_OFFLINE() + LOAD_STACK(SCENES.Offline); }); SOCKET.addEventListener("open", (event) => { if(SOCKET.readyState === WebSocket.OPEN) { diff --git a/www/js/ui.js b/www/js/ui.js index 9905bde..50bacf7 100644 --- a/www/js/ui.js +++ b/www/js/ui.js @@ -84,8 +84,8 @@ const UI = { let left = document.createElement("section"); if(CONTEXT.Auth === null) { - left.appendChild(UI.button("Register", () => { LOAD(SCENES.Register) })); - left.appendChild(UI.button("Log In", () => { LOAD(SCENES.Authenticate) })); + left.appendChild(UI.button("Register", () => { LOAD_STACK(SCENES.Register) })); + left.appendChild(UI.button("Log In", () => { LOAD_STACK(SCENES.Authenticate) })); } for(child of left_children) { left.appendChild(child); }