Add persistent storage for salt and handle.

This commit is contained in:
yukirij 2024-08-10 15:04:34 -07:00
parent d5c0d74016
commit ee12d74c67
23 changed files with 901 additions and 418 deletions

View File

@ -19,6 +19,7 @@ http-body-util = "0.1.2"
futures = "0.3.30" futures = "0.3.30"
rust-argon2 = "2.1.0" rust-argon2 = "2.1.0"
ring = "0.17.8" ring = "0.17.8"
const_format = "0.2.32"
game = { path = "../game" } game = { path = "../game" }

View File

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

View File

@ -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 {
}

View File

@ -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; use crate::app::authentication::AuthToken;
type StreamType = Arc<RwLock<SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>>>;
pub struct Connection { pub struct Connection {
pub bus:u32, pub bus:u32,
pub stream:StreamType,
pub auth:Option<AuthToken>, pub auth:Option<AuthToken>,
} }
impl Connection {
pub fn new() -> Self
{
Self {
bus:0,
auth:None,
}
}
}

View File

@ -3,9 +3,9 @@
use sparse::Sparse; use sparse::Sparse;
use pool::Pool; use pool::Pool;
use trie::Trie; use trie::Trie;
use crate::util::{ use crate::{
Chain, system::filesystem::FileSystem,
pack::pack_u32, util::Chain,
}; };
pub mod connection; use connection::Connection; pub mod connection; use connection::Connection;
@ -15,13 +15,15 @@ pub mod session; use session::{Session, SessionToken};
pub mod context; pub mod context;
pub struct App { pub struct App {
pub filesystem:FileSystem,
pub connections:Pool<Connection>, pub connections:Pool<Connection>,
pub users:Pool<User>, pub users:Pool<User>,
pub user_next:u32, pub user_next:u32,
pub user_id:Sparse<usize>, pub user_id:Sparse<usize>,
pub user_handle:Trie<u32>, pub user_handle:Trie<u32>,
pub salts:Vec<[u8; 16]>, pub salts:Sparse<[u8; 16]>,
pub auths:Trie<Authentication>, pub auths:Trie<Authentication>,
pub sessions:Trie<Session>, pub sessions:Trie<Session>,
@ -29,39 +31,45 @@ pub struct App {
pub session_time:Chain<SessionToken>, pub session_time:Chain<SessionToken>,
} }
impl App { impl App {
pub fn new() -> Self pub fn init() -> Result<Self, ()>
{ {
Self { if let Ok(mut filesystem) = FileSystem::init() {
connections:Pool::new(),
users:Pool::new(), // Load salts
user_next:0, let mut salts = Sparse::new();
user_id:Sparse::new(), let salt_count = filesystem.salt_count()?;
user_handle:Trie::new(), for id in 0..salt_count {
salts:Vec::new(), let salt = filesystem.salt_fetch(id as u32).unwrap();
salts.set(id as isize, salt);
}
auths:Trie::new(), // Load handles
sessions:Trie::new(), 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(())
}
} }

View File

@ -2,5 +2,5 @@ pub struct User {
pub id:u32, pub id:u32,
pub handle:String, pub handle:String,
pub secret:Vec<u8>, pub secret:Vec<u8>,
pub na_key:usize, pub na_key:u32,
} }

View File

@ -100,13 +100,14 @@ async fn main()
}; };
// Initialize application data. // Initialize application data.
let mut app = App::new(); let app;
if app.init().is_err() { if let Ok(result) = App::init() {
app = result;
} else {
println!("fatal: failed to initialize server."); println!("fatal: failed to initialize server.");
return; return;
} }
// Initialize central bus and data serivce. // Initialize central bus and data serivce.
let b_main :Bus<protocol::QRPacket> = Bus::new_as(bus::Mode::Transmitter); let b_main :Bus<protocol::QRPacket> = Bus::new_as(bus::Mode::Transmitter);
match b_main.connect() { match b_main.connect() {

View File

@ -1,3 +1,6 @@
use tokio_tungstenite::tungstenite::Message;
use futures::SinkExt;
use bus::Bus; use bus::Bus;
use crate::{ use crate::{
app::{ app::{
@ -7,20 +10,28 @@ use crate::{
connection::Connection, connection::Connection,
}, },
protocol, protocol,
util::pack::pack_u16,
}; };
fn encode_response(code:u16, data:Vec<u8>) -> Vec<u8>
{
[
pack_u16(code),
data,
].concat()
}
pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>) pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
{ {
use protocol::*; use protocol::*;
use protocol::QRPacketData::*;
use ring::rand::{SecureRandom, SystemRandom}; use ring::rand::{SecureRandom, SystemRandom};
let rng = SystemRandom::new(); let rng = SystemRandom::new();
let argon_config = argon2::Config::default(); let argon_config = argon2::Config::default();
while match bus.receive_wait() { while let Some(response) = match bus.receive_wait() {
Some(packet) => { Some(packet) => {
let qr = &packet.data; let qr = packet.data;
let mut user_id = None; let mut user_id = None;
if let Some(conn) = app.connections.get(qr.id as usize) { 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<protocol::QRPacket>)
} }
} }
match &qr.data { match qr.data {
QConn(request) => { QRPacketData::QConn(request) => {
let id = app.connections.add(Connection { let id = app.connections.add(Connection {
bus: request.bus_id, bus: request.bus_id,
stream: request.stream,
auth: None, auth: None,
}); });
println!("Connect: {}", id); 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 => { QRPacketData::QDisconn => {
app.connections.remove(qr.id as usize).ok(); // 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); println!("Disconnect: {}", qr.id);
true }
Some(QRPacket::new(0, QRPacketData::None))
} }
QRegister(request) => { QRPacketData::QRegister(request) => {
let mut response = PacketRegisterResponse::new(); let mut response = PacketRegisterResponse::new();
response.status = STATUS_ERROR; response.status = STATUS_SERVER_ERROR;
println!("Request: Register"); println!("Request: Register");
@ -67,8 +91,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
let mut salt = [0u8; 16]; let mut salt = [0u8; 16];
match rng.fill(&mut salt) { match rng.fill(&mut salt) {
Ok(_) => { Ok(_) => {
let salt_id = app.salts.len(); let salt_id = app.filesystem.salt_store(salt).unwrap();
app.salts.push(salt); app.salts.set(salt_id as isize, salt);
if let Ok(hash) = argon2::hash_raw(&request.secret, &salt, &argon_config) { if let Ok(hash) = argon2::hash_raw(&request.secret, &salt, &argon_config) {
let user_id = app.user_next; let user_id = app.user_next;
@ -83,6 +107,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
}); });
// Register user pool id and handle // 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_id.set(user_id as isize, user_pos);
app.user_handle.set(request.handle.as_bytes(), user_id); app.user_handle.set(request.handle.as_bytes(), user_id);
@ -120,10 +145,10 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
} }
} }
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(); let mut response = PacketAuthResponse::new();
response.status = STATUS_ERROR; response.status = STATUS_ERROR;
@ -141,7 +166,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
if let Some(tuid) = app.user_id.get(*uid as isize) { if let Some(tuid) = app.user_id.get(*uid as isize) {
if let Some(user) = app.users.get(*tuid) { if let Some(user) = app.users.get(*tuid) {
// Get user salt // 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 // 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) {
@ -184,10 +209,10 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
} }
} }
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(); let mut response = PacketAuthResumeResponse::new();
response.status = STATUS_ERROR; response.status = STATUS_ERROR;
@ -209,10 +234,10 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
} }
} }
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) { if let Some(conn) = app.connections.get_mut(qr.id as usize) {
match conn.auth { match conn.auth {
Some(auth) => { Some(auth) => {
@ -223,10 +248,10 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
} }
conn.auth = None; conn.auth = None;
} }
true Some(QRPacket::new(0, QRPacketData::None))
} }
QSessionList(request) => { QRPacketData::QSessionList(request) => {
use game::game::GameState; use game::game::GameState;
println!("Request: Session List"); println!("Request: Session List");
@ -277,7 +302,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
dusk_handle, dusk_handle,
], ],
turn:0, turn:0,
last_move:[0; 5], last_move:[0; 3],
viewers:0, viewers:0,
player:is_player, player:is_player,
}); });
@ -290,10 +315,10 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
next_id = app.session_time.next(id); 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::*; use crate::app::session::*;
println!("Request: Session Create"); println!("Request: Session Create");
@ -336,10 +361,10 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
response.token = token; 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"); println!("Request: Session Join");
let mut response = PacketSessionJoinResponse::new(); let mut response = PacketSessionJoinResponse::new();
@ -390,12 +415,58 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
} }
} }
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();
}
_ => { }
}
}
}
}
} }

View File

@ -1,45 +1,42 @@
use std::sync::Arc;
use tokio::sync::RwLock;
use hyper::upgrade::Upgraded; use hyper::upgrade::Upgraded;
use hyper_util::rt::TokioIo; use hyper_util::rt::TokioIo;
use tokio_tungstenite::WebSocketStream; use tokio_tungstenite::WebSocketStream;
use futures::{SinkExt, StreamExt}; use futures::StreamExt;
use crate::{ use crate::{
protocol, protocol,
util::pack::{pack_u16, unpack_u16}, util::pack::unpack_u16,
HttpServiceArgs, HttpServiceArgs,
}; };
fn encode_response(code:u16, data:Vec<u8>) -> Vec<u8> pub async fn handle_ws(ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServiceArgs) -> Result<(),()>
{
[
pack_u16(code),
data,
].concat()
}
pub async fn handle_ws(mut ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServiceArgs) -> Result<(),()>
// Handle websocket connection. // Handle websocket connection.
// //
{ {
use tokio_tungstenite::tungstenite::protocol::Message; use tokio_tungstenite::tungstenite::protocol::Message;
use protocol::{QRPacket, QRPacketData::*, code::*, packet::*}; use protocol::{QRPacket, QRPacketData, code::*, packet::*};
let conn_id :u32; let conn_id :u32;
let (sink, mut stream) = ws.split();
let bus_ds = args.bus.mailbox(1).unwrap_or(1); let bus_ds = args.bus.mailbox(1).unwrap_or(1);
// Perform connection handshake with data system. // Perform connection handshake with data system.
// - Provide system with connection/bus pairing. // - Provide system with connection/bus pairing.
// - Acquire connection id. // - 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(), bus_id:args.bus.id(),
stream:Arc::new(RwLock::new(sink)),
})))?; })))?;
match args.bus.receive_wait() { match args.bus.receive_wait() {
Some(resp) => { Some(resp) => {
let qr = &resp.data; let qr = &resp.data;
match qr.data { match qr.data {
RConn => { conn_id = qr.id; } QRPacketData::RConn => { conn_id = qr.id; }
_ => { return Err(()); } _ => { return Err(()); }
} }
} }
@ -49,7 +46,7 @@ pub async fn handle_ws(mut ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServi
// Decode client requests from websocket, // Decode client requests from websocket,
// pass requests to data system, // pass requests to data system,
// and return responses to client. // and return responses to client.
while match ws.next().await { while match stream.next().await {
Some(msg) => match msg { Some(msg) => match msg {
Ok(msg) => match msg { Ok(msg) => match msg {
Message::Binary(data) => { Message::Binary(data) => {
@ -62,142 +59,67 @@ pub async fn handle_ws(mut ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServi
CODE_REGISTER => match PacketRegister::decode(&data, &mut index) { CODE_REGISTER => match PacketRegister::decode(&data, &mut index) {
Ok(packet) => { Ok(packet) => {
if args.bus.send(bus_ds, QRPacket::new(conn_id, QRegister(packet))).is_ok() { args.bus.send(
while match args.bus.receive_wait() { bus_ds,
Some(resp) => { QRPacket::new(conn_id, QRPacketData::QRegister(packet))
let qr = &resp.data; ).ok();
match &qr.data {
RRegister(resp) => {
ws.send(Message::Binary(
encode_response(code, resp.encode())
)).await.ok();
false
}
_ => true,
}
}
None => true,
} { }
}
} }
Err(_) => { } Err(_) => { }
} }
CODE_AUTH => match PacketAuth::decode(&data, &mut index) { CODE_AUTH => match PacketAuth::decode(&data, &mut index) {
Ok(packet) => { Ok(packet) => {
if args.bus.send(bus_ds, QRPacket::new(conn_id, QAuth(packet))).is_ok() { args.bus.send(
while match args.bus.receive_wait() { bus_ds,
Some(resp) => { QRPacket::new(conn_id, QRPacketData::QAuth(packet))
let qr = &resp.data; ).ok();
match &qr.data {
RAuth(resp) => {
ws.send(Message::Binary(
encode_response(code, resp.encode())
)).await.ok();
false
}
_ => true,
}
}
None => true,
} { }
}
} }
Err(_) => { } Err(_) => { }
} }
CODE_AUTH_RESUME => match PacketAuthResume::decode(&data, &mut index) { CODE_AUTH_RESUME => match PacketAuthResume::decode(&data, &mut index) {
Ok(packet) => { Ok(packet) => {
if args.bus.send(bus_ds, QRPacket::new(conn_id, QAuthResume(packet))).is_ok() { args.bus.send(
while match args.bus.receive_wait() { bus_ds,
Some(resp) => { QRPacket::new(conn_id, QRPacketData::QAuthResume(packet))
let qr = &resp.data; ).ok();
match &qr.data {
RAuth(resp) => {
ws.send(Message::Binary(
encode_response(code, resp.encode())
)).await.ok();
false
}
_ => true,
}
}
None => true,
} { }
}
} }
Err(_) => { } Err(_) => { }
} }
CODE_DEAUTH => { CODE_AUTH_REVOKE => {
args.bus.send(bus_ds, QRPacket::new(conn_id, QDeauth)).ok(); args.bus.send(
bus_ds, QRPacket::new(conn_id,
QRPacketData::QAuthRevoke)
).ok();
} }
CODE_SESSION_LIST => match PacketSessionList::decode(&data, &mut index) { CODE_SESSION_LIST => match PacketSessionList::decode(&data, &mut index) {
Ok(packet) => { Ok(packet) => {
if args.bus.send(bus_ds, QRPacket::new(conn_id, QSessionList(packet))).is_ok() { args.bus.send(
while match args.bus.receive_wait() { bus_ds,
Some(resp) => { QRPacket::new(conn_id, QRPacketData::QSessionList(packet))
let qr = &resp.data; ).ok();
match &qr.data {
RSessionList(resp) => {
ws.send(Message::Binary(
encode_response(code, resp.encode())
)).await.ok();
false
}
_ => true,
}
}
None => true,
} { }
}
} }
Err(_) => { println!("error: packet decode failed."); } Err(_) => { println!("error: packet decode failed."); }
} }
CODE_SESSION_CREATE => match PacketSessionCreate::decode(&data, &mut index) { CODE_SESSION_CREATE => match PacketSessionCreate::decode(&data, &mut index) {
Ok(packet) => { Ok(packet) => {
if args.bus.send(bus_ds, QRPacket::new(conn_id, QSessionCreate(packet))).is_ok() { args.bus.send(
while match args.bus.receive_wait() { bus_ds,
Some(resp) => { QRPacket::new(conn_id, QRPacketData::QSessionCreate(packet))
let qr = &resp.data; ).ok();
match &qr.data {
RSessionCreate(resp) => {
ws.send(Message::Binary(
encode_response(code, resp.encode())
)).await.ok();
false
}
_ => true,
}
}
None => true,
} { }
}
} }
Err(_) => { println!("error: packet decode failed."); } Err(_) => { println!("error: packet decode failed."); }
} }
CODE_SESSION_JOIN => match PacketSessionJoin::decode(&data, &mut index) { CODE_SESSION_JOIN => match PacketSessionJoin::decode(&data, &mut index) {
Ok(packet) => { Ok(packet) => {
if args.bus.send(bus_ds, QRPacket::new(conn_id, QSessionJoin(packet))).is_ok() { args.bus.send(
while match args.bus.receive_wait() { bus_ds,
Some(resp) => { QRPacket::new(conn_id, QRPacketData::QSessionJoin(packet))
let qr = &resp.data; ).ok();
match &qr.data {
RSessionJoin(resp) => {
ws.send(Message::Binary(
encode_response(code, resp.encode())
)).await.ok();
false
}
_ => true,
}
}
None => true,
} { }
}
} }
Err(_) => { println!("error: packet decode failed."); } Err(_) => { println!("error: packet decode failed."); }
} }
@ -216,7 +138,6 @@ pub async fn handle_ws(mut ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServi
None => false, None => false,
} { } } { }
args.bus.send(bus_ds, QRPacket::new(conn_id, QDisconn)).ok(); args.bus.send(bus_ds, QRPacket::new(conn_id, QRPacketData::QDisconn)).ok();
ws.close(None).await.ok();
Ok(()) Ok(())
} }

View File

@ -20,8 +20,8 @@ pub const STATUS_NOT_IMPL :u16 = 0x00FF;
pub const CODE_REGISTER :u16 = 0x0010; pub const CODE_REGISTER :u16 = 0x0010;
pub const CODE_AUTH :u16 = 0x0011; pub const CODE_AUTH :u16 = 0x0011;
pub const CODE_DEAUTH :u16 = 0x0013;
pub const CODE_AUTH_RESUME :u16 = 0x0012; 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_LIST :u16 = 0x0020;
pub const CODE_SESSION_CREATE :u16 = 0x0021; 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_RETIRE :u16 = 0x002E;
pub const CODE_SESSION_LEAVE :u16 = 0x002F; 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;

View File

@ -5,7 +5,7 @@ pub mod packet; pub use packet::*;
#[derive(Clone)] #[derive(Clone)]
pub enum QRPacketData { pub enum QRPacketData {
ROk, None,
QConn(LocalPacketConnect), QConn(LocalPacketConnect),
RConn, RConn,
@ -21,7 +21,7 @@ pub enum QRPacketData {
QAuthResume(PacketAuthResume), QAuthResume(PacketAuthResume),
RAuthResume(PacketAuthResumeResponse), RAuthResume(PacketAuthResumeResponse),
QDeauth, QAuthRevoke,
QSessionList(PacketSessionList), QSessionList(PacketSessionList),
RSessionList(PacketSessionListResponse), RSessionList(PacketSessionListResponse),

View File

@ -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 struct LocalPacketConnect {
pub bus_id:u32, pub bus_id:u32,
pub stream:Arc<RwLock<SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>>>,
} }

View File

@ -65,7 +65,7 @@ pub struct PacketSessionListResponseRecord {
pub token:SessionToken, pub token:SessionToken,
pub handles:[String; 2], pub handles:[String; 2],
pub turn:u16, pub turn:u16,
pub last_move:[u8; 5], pub last_move:[u8; 3],
pub viewers:u32, pub viewers:u32,
pub player:bool, pub player:bool,
} }

View File

@ -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<Self, std::io::Error>
{
//
// 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<u32,()>
// 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<usize, ()>
// Get number of salts in store.
//
{
Self::get_header_size(&mut self.index_handle)
}
pub fn salt_store(&mut self, salt:[u8; 16]) -> Result<u32,()>
// 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<usize, ()>
// Get number of salts in store.
//
{
Self::get_header_size(&mut self.table_salt)
}
fn get_header_size(file:&mut File) -> Result<usize, ()>
{
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(()) }
}
}

View File

@ -2,3 +2,4 @@
pub mod net; pub mod net;
pub mod cache; pub mod cache;
pub mod filesystem;

View File

@ -3,7 +3,7 @@ pub fn pack_u8(value:u8) -> Vec<u8>
vec![value] vec![value]
} }
pub fn unpack_u8(data:&Vec<u8>, index:&mut usize) -> u8 pub fn unpack_u8(data:&[u8], index:&mut usize) -> u8
{ {
let mut result :u8 = 0; let mut result :u8 = 0;
if *index < data.len() { if *index < data.len() {
@ -18,7 +18,7 @@ pub fn pack_u16(value:u16) -> Vec<u8>
vec![(value >> 8) as u8, (value & 0xFF) as u8] vec![(value >> 8) as u8, (value & 0xFF) as u8]
} }
pub fn unpack_u16(data:&Vec<u8>, index:&mut usize) -> u16 pub fn unpack_u16(data:&[u8], index:&mut usize) -> u16
{ {
let mut result :u16 = 0; let mut result :u16 = 0;
if *index < data.len() { if *index < data.len() {
@ -42,7 +42,7 @@ pub fn pack_u32(value:u32) -> Vec<u8>
] ]
} }
pub fn unpack_u32(data:&Vec<u8>, index:&mut usize) -> u32 pub fn unpack_u32(data:&[u8], index:&mut usize) -> u32
{ {
let mut result :u32 = 0; let mut result :u32 = 0;
if *index < data.len() { if *index < data.len() {

View File

@ -11,13 +11,13 @@ let CONTEXT = {
}; };
const Status = { const Status = {
Ok: 0, Ok: 0x0000,
Error: 1, Error: 0x0001,
NotImplement: 2, NotImplemented: 0x0002,
BadHandle: 1, BadHandle: 0x0010,
BadSecret: 2, BadSecret: 0x0011,
BadCode: 3, BadCode: 0x0012,
}; };
const OpCode = { const OpCode = {
@ -29,8 +29,11 @@ const OpCode = {
SessionList :0x0020, SessionList :0x0020,
SessionCreate :0x0021, SessionCreate :0x0021,
SessionJoin :0x0022, SessionJoin :0x0022,
SessionRetire :0x002E,
SessionLeave :0x002F,
GameState :0x0030, GameState :0x0030,
GamePlay :0x0031,
}; };
const GameState = { const GameState = {

View File

@ -1,157 +1,212 @@
class GamePieceMove { const GAME_CONST = {
PLAYER_DAWN: 0,
PLAYER_DUSK: 1,
} SOURCE_BOARD: 0,
SOURCE_POOL: 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)
),
],
}; };
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 = { const GAME = {
init() { init() {
GAME_DATA.board.tiles GAME_DATA = new GAME_CLASS.Game();
},
process(move) {
},
validate(move) {
}, },
}; };

View File

@ -53,7 +53,11 @@ const INTERFACE = {
}, },
init() { init() {
GAME.init();
INTERFACE_DATA.canvas = document.getElementById("game"); INTERFACE_DATA.canvas = document.getElementById("game");
let canvas = INTERFACE_DATA.canvas;
if(canvas !== undefined) { if(canvas !== undefined) {
INTERFACE_DATA.context = canvas.getContext("2d"); INTERFACE_DATA.context = canvas.getContext("2d");
@ -68,4 +72,8 @@ const INTERFACE = {
INTERFACE_DATA.canvas = null; INTERFACE_DATA.canvas = null;
INTERFACE_DATA.context = null; INTERFACE_DATA.context = null;
}, },
message(data) {
},
}; };

View File

@ -1,4 +1,6 @@
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
SCENE = SCENES.Offline; SCENE = SCENES.Offline;
LOAD(SCENES.Init); LOAD(SCENES.Init);
document.addEventListener("beforeunload", UNLOAD);
}); });

View File

@ -1,7 +1,7 @@
const SCENES = { const SCENES = {
Init:{ Init:{
load() { load() {
LOAD_OFFLINE(); LOAD_STACK(SCENES.Offline);
CONTEXT.Scene = SCENES.Online; CONTEXT.Scene = SCENES.Online;
RECONNECT(); RECONNECT();
return true; return true;
@ -210,11 +210,13 @@ const SCENES = {
MESSAGE_SESSION_LIST(0, 0, false, false); MESSAGE_SESSION_LIST(0, 0, false, false);
}, },
message(code, data) { message(code, data) {
let table = document.getElementById("content"); if(code == OpCode.SessionList) {
UI.clear(table); let table = document.getElementById("content");
UI.clear(table);
if(data !== null) { if(data !== null) {
table.appendChild(UI.session_table(data.records)); table.appendChild(UI.session_table(data.records));
}
} }
} }
}, },
@ -250,6 +252,16 @@ const SCENES = {
refresh() { 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:{ Join:{
@ -283,6 +295,16 @@ const SCENES = {
refresh() { 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:{ Live:{
@ -315,6 +337,16 @@ const SCENES = {
refresh() { 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:{ History:{
@ -342,12 +374,25 @@ const SCENES = {
refresh() { 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:{ Guide:{
load() { load() {
UI.mainmenu(); UI.mainmenu();
UI.mainnav([], []); UI.mainnav([], []);
return true; return true;
}, },
refresh() { refresh() {
@ -359,6 +404,9 @@ const SCENES = {
load() { load() {
UI.mainmenu(); UI.mainmenu();
UI.mainnav([], []); UI.mainnav([], []);
return true; return true;
}, },
refresh() { refresh() {
@ -377,7 +425,6 @@ const SCENES = {
let canvas = document.createElement("canvas"); let canvas = document.createElement("canvas");
canvas.setAttribute("id", "game"); canvas.setAttribute("id", "game");
MAIN.appendChild(canvas); MAIN.appendChild(canvas);
INTERFACE.init(); INTERFACE.init();
@ -385,29 +432,33 @@ const SCENES = {
return true; return true;
}, },
unload() { unload() {
MESSAGE_COMPOSE([
PACK.u16(OpCode.SessionLeave),
]);
}, },
refresh() { message(code, data) {
if(code == OpCode.GameState || code == OpCode.GamePlay) {
}, INTERFACE.message(code, data);
message() { }
}, },
}, },
}; };
function LOAD(scene) { function LOAD(scene) {
if(SCENE.unload !== undefined) { SCENE.unload(); } UNLOAD();
UI.rebuild(); UI.rebuild();
SCENE = scene; SCENE = scene;
CONTEXT.Scene = SCENE; CONTEXT.Scene = SCENE;
if(!SCENE.load()) { LOAD(SCENES.Online); } if(!SCENE.load()) { LOAD(SCENES.Online); }
} }
function LOAD_OFFLINE() { function UNLOAD() {
if(SCENE.unload !== undefined) { SCENE.unload(); } if(SCENE !== null && SCENE.unload !== undefined) { SCENE.unload(); }
while(document.body.lastChild !== null) { document.body.removeChild(document.body.lastChild); } }
function LOAD_STACK(scene) {
UNLOAD();
UI.rebuild(); UI.rebuild();
SCENE = SCENES.Offline; SCENE = scene;
if(!SCENE.load()) { LOAD(SCENES.Online); } if(!SCENE.load()) { LOAD(SCENES.Online); }
} }

View File

@ -5,7 +5,7 @@ function RECONNECT() {
SOCKET.binaryType = "arraybuffer"; SOCKET.binaryType = "arraybuffer";
SOCKET.addEventListener("error", (event) => { SOCKET.addEventListener("error", (event) => {
SOCKET = null; SOCKET = null;
LOAD_OFFLINE() LOAD_STACK(SCENES.Offline);
}); });
SOCKET.addEventListener("open", (event) => { SOCKET.addEventListener("open", (event) => {
if(SOCKET.readyState === WebSocket.OPEN) { if(SOCKET.readyState === WebSocket.OPEN) {

View File

@ -84,8 +84,8 @@ const UI = {
let left = document.createElement("section"); let left = document.createElement("section");
if(CONTEXT.Auth === null) { if(CONTEXT.Auth === null) {
left.appendChild(UI.button("Register", () => { LOAD(SCENES.Register) })); left.appendChild(UI.button("Register", () => { LOAD_STACK(SCENES.Register) }));
left.appendChild(UI.button("Log In", () => { LOAD(SCENES.Authenticate) })); left.appendChild(UI.button("Log In", () => { LOAD_STACK(SCENES.Authenticate) }));
} }
for(child of left_children) { left.appendChild(child); } for(child of left_children) { left.appendChild(child); }