Replace session create with challenge.

This commit is contained in:
yukirij 2024-08-25 20:35:13 -07:00
parent 44ef10e6c2
commit cd9a0aea2c
20 changed files with 1268 additions and 663 deletions

View File

@ -66,7 +66,7 @@ impl App {
let user = filesystem.user_fetch(id as u32).unwrap();
// Add user to contests if flag is set.
if (user.flags & user::F_CONTEST) != 0 {
if (user.flags & user::FC_ENABLE) != 0 {
match contests.binary_search(&user.id) {
Ok(_) => { }
Err(pos) => { contests.insert(pos, user.id); }
@ -135,6 +135,13 @@ impl App {
} else { None }
}
pub fn get_user_by_id_mut(&mut self, id:u32) -> Option<&mut User>
{
if let Some(uid) = self.user_id.get(id as isize) {
self.users.get_mut(*uid)
} else { None }
}
pub async fn send_response(&mut self, response:QRPacket)
{
use crate::protocol::*;
@ -178,15 +185,9 @@ impl App {
)).await.ok();
}
QRPacketData::RSessionCreate(response) => {
QRPacketData::RSessionView(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())
encode_response(CODE_SESSION_VIEW, response.encode())
)).await.ok();
}
@ -202,6 +203,24 @@ impl App {
)).await.ok();
}
QRPacketData::RChallengeAnswer(response) => {
socket.send(Message::Binary(
encode_response(CODE_CHALLENGE_ANSWER, response.encode())
)).await.ok();
}
QRPacketData::RChallengeList(response) => {
socket.send(Message::Binary(
encode_response(CODE_CHALLENGE_LIST, response.encode())
)).await.ok();
}
QRPacketData::RUserList(response) => {
socket.send(Message::Binary(
encode_response(CODE_USER_LIST, response.encode())
)).await.ok();
}
_ => { }
}
}

View File

@ -1,4 +1,16 @@
pub const F_CONTEST :u32 = 0x0000_0001;
pub const FC_ENABLE :u32 = 0x0000_0001;
pub struct UserStatus {
pub await_flags:u32,
}
impl UserStatus {
pub fn new() -> Self
{
Self {
await_flags:0,
}
}
}
pub struct User {
pub id:u32,
@ -6,4 +18,7 @@ pub struct User {
pub handle:String,
pub secret:Vec<u8>,
pub na_key:u32,
pub status:UserStatus,
pub challenges:Vec<u32>,
}

File diff suppressed because it is too large Load Diff

View File

@ -104,7 +104,7 @@ pub async fn handle_ws(ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServiceAr
Err(_) => { println!("error: packet decode failed."); }
}
CODE_SESSION_CREATE => match PacketSessionCreate::decode(&data, &mut index) {
/*CODE_SESSION_JOIN => match PacketSessionCreate::decode(&data, &mut index) {
Ok(packet) => {
args.bus.send(
bus_ds,
@ -112,13 +112,13 @@ pub async fn handle_ws(ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServiceAr
).ok();
}
Err(_) => { println!("error: packet decode failed."); }
}
}*/
CODE_SESSION_JOIN => match PacketSessionJoin::decode(&data, &mut index) {
CODE_SESSION_VIEW => match PacketSessionView::decode(&data, &mut index) {
Ok(packet) => {
args.bus.send(
bus_ds,
QRPacket::new(conn_id, QRPacketData::QSessionJoin(packet))
QRPacket::new(conn_id, QRPacketData::QSessionView(packet))
).ok();
}
Err(_) => { println!("error: packet decode failed."); }
@ -161,6 +161,40 @@ pub async fn handle_ws(ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServiceAr
Err(_) => { println!("error: packet decode failed."); }
}
CODE_CHALLENGE => match PacketChallenge::decode(&data, &mut index) {
Ok(packet) => {
args.bus.send(
bus_ds,
QRPacket::new(conn_id, QRPacketData::QChallenge(packet))
).ok();
}
Err(_) => { println!("error: packet decode failed."); }
}
CODE_CHALLENGE_ANSWER => match PacketChallengeAnswer::decode(&data, &mut index) {
Ok(packet) => {
args.bus.send(
bus_ds,
QRPacket::new(conn_id, QRPacketData::QChallengeAnswer(packet))
).ok();
}
Err(_) => { println!("error: packet decode failed."); }
}
CODE_CHALLENGE_LIST => {
args.bus.send(
bus_ds,
QRPacket::new(conn_id, QRPacketData::QChallengeList)
).ok();
}
CODE_USER_LIST => {
args.bus.send(
bus_ds,
QRPacket::new(conn_id, QRPacketData::QUserList)
).ok();
}
_ => { }
}
true

View File

@ -3,9 +3,13 @@
*/
pub const STATUS_OK :u16 = 0x0000;
pub const STATUS_ERROR :u16 = 0x0001;
pub const STATUS_NOAUTH :u16 = 0x0002;
pub const STATUS_APPROVE :u16 = 0x0003;
pub const STATUS_REJECT :u16 = 0x0004;
pub const STATUS_BAD_HANDLE :u16 = 0x0010;
pub const STATUS_BAD_SECRET :u16 = 0x0011;
pub const STATUS_BAD_CODE :u16 = 0x0012;
@ -24,10 +28,18 @@ 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;
pub const CODE_SESSION_JOIN :u16 = 0x0022;
pub const CODE_SESSION_JOIN :u16 = 0x0021;
pub const CODE_SESSION_VIEW :u16 = 0x0022;
pub const CODE_SESSION_RETIRE :u16 = 0x002E;
pub const CODE_SESSION_LEAVE :u16 = 0x002F;
pub const CODE_GAME_STATE :u16 = 0x0030;
pub const CODE_GAME_PLAY :u16 = 0x0031;
pub const CODE_CHALLENGE :u16 = 0x0060;
pub const CODE_CHALLENGE_ANSWER :u16 = 0x0061;
pub const CODE_CHALLENGE_LIST :u16 = 0x0062;
pub const CODE_USER_LIST :u16 = 0x0100;
//pub const CODE_USER_AWAIT_GET :u16 = 0x0110;
//pub const CODE_USER_AWAIT_SET :u16 = 0x0111;

View File

@ -23,17 +23,14 @@ pub enum QRPacketData {
QAuthRevoke,
QUserList(PacketUserList),
QUserList,
RUserList(PacketUserListResponse),
QSessionList(PacketSessionList),
RSessionList(PacketSessionListResponse),
QSessionCreate(PacketSessionCreate),
RSessionCreate(PacketSessionCreateResponse),
QSessionJoin(PacketSessionJoin),
RSessionJoin(PacketSessionJoinResponse),
QSessionView(PacketSessionView),
RSessionView(PacketSessionViewResponse),
QSessionRetire(PacketSessionRetire),
@ -43,6 +40,14 @@ pub enum QRPacketData {
RGameState(PacketGameStateResponse),
QGamePlay(PacketGamePlay),
QChallenge(PacketChallenge),
QChallengeAnswer(PacketChallengeAnswer),
RChallengeAnswer(PacketChallengeAnswerResponse),
QChallengeList,
RChallengeList(PacketChallengeListResponse),
}
#[derive(Clone)]

View File

@ -0,0 +1,38 @@
use crate::util::pack::unpack_u8;
use super::Packet;
#[derive(Clone)]
pub struct PacketChallenge {
pub handle:String,
}
impl PacketChallenge {
pub fn new() -> Self
{
Self {
handle:String::new(),
}
}
}
impl Packet for PacketChallenge {
type Data = Self;
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>
{
let mut result = Self::new();
let length = unpack_u8(data, index) as usize;
let mut buffer = vec![0u8; length];
if data.len() - *index >= length {
for i in 0..length {
buffer[i] = data[*index];
*index += 1;
}
result.handle = String::from_utf8(buffer).map_err(|_| ())?;
Ok(result)
} else {
Err(())
}
}
}

View File

@ -0,0 +1,73 @@
use crate::{
app::session::SessionToken,
util::pack::{unpack_u8, pack_u16},
};
use super::Packet;
#[derive(Clone)]
pub struct PacketChallengeAnswer {
pub handle:String,
pub answer:bool,
}
impl PacketChallengeAnswer {
pub fn new() -> Self
{
Self {
handle:String::new(),
answer:false,
}
}
}
impl Packet for PacketChallengeAnswer {
type Data = Self;
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>
{
let mut result = Self::new();
result.answer = unpack_u8(data, index) != 0;
let length = unpack_u8(data, index) as usize;
let mut buffer = vec![0u8; length];
if data.len() - *index >= length {
for i in 0..length {
buffer[i] = data[*index];
*index += 1;
}
result.handle = String::from_utf8(buffer).map_err(|_| ())?;
Ok(result)
} else {
Err(())
}
}
}
#[derive(Clone)]
pub struct PacketChallengeAnswerResponse {
pub status:u16,
pub token:SessionToken,
}
impl PacketChallengeAnswerResponse {
pub fn new() -> Self
{
Self {
status:0,
token:SessionToken::default(),
}
}
}
impl Packet for PacketChallengeAnswerResponse {
type Data = Self;
fn encode(&self) -> Vec<u8>
{
let mut buffer = Vec::new();
buffer.append(&mut pack_u16(self.status));
buffer.append(&mut self.token.to_vec());
buffer
}
}

View File

@ -0,0 +1,46 @@
use crate::util::pack::{pack_u8, pack_u16};
use super::Packet;
#[derive(Clone)]
pub struct PacketChallengeListData {
pub handle:String,
}
#[derive(Clone)]
pub struct PacketChallengeListResponse {
pub status:u16,
pub challenges:Vec<PacketChallengeListData>,
}
impl PacketChallengeListResponse {
pub fn new() -> Self
{
Self {
status:0,
challenges:Vec::new(),
}
}
}
impl Packet for PacketChallengeListResponse {
type Data = Self;
fn encode(&self) -> Vec<u8>
{
let mut buffer = pack_u16(self.status);
// Records
buffer.append(&mut pack_u16(self.challenges.len() as u16));
for challenge in &self.challenges {
let mut chunk = Vec::new();
// Handle
let mut bytes = challenge.handle.as_bytes().to_vec();
chunk.append(&mut pack_u8(bytes.len() as u8));
chunk.append(&mut bytes);
buffer.append(&mut chunk);
}
buffer
}
}

View File

@ -4,17 +4,21 @@ mod register; pub use register::*;
mod auth; pub use auth::*;
mod resume; pub use resume::*;
mod user_list; pub use user_list::*;
mod session_list; pub use session_list::*;
mod session_create; pub use session_create::*;
mod session_join; pub use session_join::*;
//mod session_create; pub use session_create::*;
mod session_view; pub use session_view::*;
mod session_retire; pub use session_retire::*;
mod game_state; pub use game_state::*;
mod game_play; pub use game_play::*;
//mod game_history; pub use game_history::*;
mod challenge; pub use challenge::*;
mod challenge_answer; pub use challenge_answer::*;
mod challenge_list; pub use challenge_list::*;
mod user_list; pub use user_list::*;
mod prelude {
pub trait Packet {
type Data;

View File

@ -6,11 +6,11 @@ use crate::{
use super::Packet;
#[derive(Clone)]
pub struct PacketSessionJoin {
pub struct PacketSessionView {
pub token:SessionToken,
pub join:bool,
}
impl PacketSessionJoin {
impl PacketSessionView {
pub fn new() -> Self
{
Self {
@ -19,7 +19,7 @@ impl PacketSessionJoin {
}
}
}
impl Packet for PacketSessionJoin {
impl Packet for PacketSessionView {
type Data = Self;
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>
@ -38,11 +38,11 @@ impl Packet for PacketSessionJoin {
#[derive(Clone)]
pub struct PacketSessionJoinResponse {
pub struct PacketSessionViewResponse {
pub status:u16,
pub token:SessionToken,
}
impl PacketSessionJoinResponse {
impl PacketSessionViewResponse {
pub fn new() -> Self
{
Self {
@ -51,7 +51,7 @@ impl PacketSessionJoinResponse {
}
}
}
impl Packet for PacketSessionJoinResponse {
impl Packet for PacketSessionViewResponse {
type Data = Self;
fn encode(&self) -> Vec<u8>

View File

@ -1,47 +1,16 @@
use crate::util::pack::{pack_u16, unpack_u16};
use crate::util::pack::{pack_u8, pack_u16};
use super::Packet;
#[derive(Clone)]
pub struct PacketUserList {
pub page:u16,
}
impl PacketUserList {
pub fn new() -> Self
{
Self {
page:0,
}
}
}
impl Packet for PacketUserList {
type Data = Self;
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>
{
let mut result = Self::new();
if data.len() - *index == 2 {
result.page = unpack_u16(data, index);
Ok(result)
} else {
Err(())
}
}
}
#[derive(Clone)]
pub struct PacketUserListResponseRecord {
//pub token:UserToken,
pub struct PacketUserListData {
pub handle:String,
}
#[derive(Clone)]
pub struct PacketUserListResponse {
pub status:u16,
pub records:Vec<PacketUserListResponseRecord>,
pub records:Vec<PacketUserListData>,
}
impl PacketUserListResponse {
pub fn new() -> Self
@ -59,15 +28,15 @@ impl Packet for PacketUserListResponse {
{
let mut result = pack_u16(self.status as u16);
// Records
result.append(&mut pack_u16(self.records.len() as u16));
for record in &self.records {
let mut chunk = Vec::new(); //record.token.to_vec();
let mut chunk = Vec::new();
// Handle
let mut bytes = record.handle.as_bytes().to_vec();
chunk.append(&mut pack_u16(bytes.len() as u16));
if bytes.len() > 0 { chunk.append(&mut bytes); }
chunk.append(&mut pack_u8(bytes.len() as u8));
chunk.append(&mut bytes);
result.append(&mut chunk);
}

View File

@ -10,7 +10,7 @@ use game::{
use crate::{
app::{
session::{Session, SessionToken, SessionSecret},
user::User,
user::{User, UserStatus},
},
util::pack::*,
};
@ -19,8 +19,10 @@ const HANDLE_BUCKET_MASK :u32 = 0xFF;
const HANDLE_BUCKET_SIZE :u32 = HANDLE_BUCKET_MASK + 1;
const GENERIC_CONFIG :&str = "c.bin";
const GENERIC_INDEX :&str = "i.bin";
const GENERIC_HISTORY :&str = "h.bin";
const GENERIC_INDEX :&str = "i.bin";
const GENERIC_REQUEST :&str = "r.bin";
const GENERIC_STATUS :&str = "s.bin";
const DIR_DATA :&str = "data";
const DIR_HANDLE :&str = const_format::formatcp!("{}/h", DIR_DATA);
@ -93,7 +95,7 @@ impl FileSystem {
}
pub fn session_store(&mut self, session:&Session) -> Result<u32,()>
pub fn session_create(&mut self, session:&Session) -> Result<u32,()>
{
let size = self.session_count()? as u32;
@ -284,7 +286,7 @@ impl FileSystem {
}
pub fn user_store(&mut self, user:&User) -> Result<u32,()>
pub fn user_create(&mut self, user:&User) -> Result<u32,()>
{
let size = self.user_count()? as u32;
@ -295,14 +297,14 @@ impl FileSystem {
self.index_user.seek(SeekFrom::Start(0)).map_err(|_| ())?;
self.index_user.write(&pack_u32(size + 1)).map_err(|_| ())?;
// Create bucket file if not exists
let bucket_path = Path::new(DIR_USER).join(format!("{:08x}", bucket_index));
if !bucket_path.exists() {
fs::create_dir(bucket_path.clone()).map_err(|_| ())?;
}
// Create user directory
let bucket_path = Path::new(DIR_USER)
.join(format!("{:08x}", bucket_index))
.join(format!("{:08x}", file_index));
fs::create_dir_all(bucket_path.clone()).map_err(|_| ())?;
// Open bucket file for record
let file_path = bucket_path.join(format!("{:08x}.bin", file_index));
// Create configuration file
let file_path = bucket_path.join(GENERIC_CONFIG);
if let Ok(mut file) = File::options().write(true).create(true).open(file_path) {
let handle = user.handle.as_bytes().to_vec();
@ -314,9 +316,35 @@ impl FileSystem {
file.write(&user.secret).map_err(|_| ())?;
file.write(&pack_u8(handle.len() as u8)).map_err(|_| ())?;
file.write(&handle).map_err(|_| ())?;
}
Ok(size)
} else { Err(()) }
// Create status file
let file_path = bucket_path.join(GENERIC_STATUS);
if let Ok(mut file) = File::options().write(true).create(true).open(file_path) {
let extra_buffer = [0u8; 12];
let contest = 0;
// Write user information
file.write(&pack_u32(contest)).map_err(|_| ())?;
file.write(&extra_buffer).map_err(|_| ())?;
}
// Create challenges file
let file_path = bucket_path.join(GENERIC_REQUEST);
if let Ok(mut file) = File::options().write(true).create(true).open(file_path) {
// Write requests count
file.write(&pack_u16(0)).map_err(|_| ())?;
}
Ok(size)
}
pub fn user_update_status(&mut self) -> Result<(),()>
{
Err(())
}
pub fn user_fetch(&mut self, id:u32) -> Result<User,()>
@ -329,7 +357,8 @@ impl FileSystem {
// Open bucket file for record
let file_path = Path::new(DIR_USER)
.join(format!("{:08x}", bucket_index))
.join(format!("{:08x}.bin", file_index));
.join(format!("{:08x}", file_index))
.join(GENERIC_CONFIG);
if let Ok(mut file) = File::options().read(true).open(file_path) {
file.seek(SeekFrom::Start(0)).map_err(|_| ())?;
@ -365,6 +394,9 @@ impl FileSystem {
handle,
secret,
na_key,
status:UserStatus::new(),
challenges:Vec::new(),
})
} else { Err(()) }
}
@ -377,7 +409,7 @@ impl FileSystem {
}
pub fn handle_store(&mut self, handle:&String, user_id:u32) -> Result<u32,()>
pub fn handle_create(&mut self, handle:&String, user_id:u32) -> Result<u32,()>
// Add a salt to store.
//
{
@ -474,7 +506,7 @@ impl FileSystem {
}
pub fn salt_store(&mut self, salt:[u8; 16]) -> Result<u32,()>
pub fn salt_create(&mut self, salt:[u8; 16]) -> Result<u32,()>
// Add a salt to store.
//
{

View File

@ -16,7 +16,10 @@ main.form>section>div{
max-width:30rem;
padding:0.5rem;
font-size: 1.2rem;
background-color:#303030;
color: #e0e0e0;
}
main.form>section>div>table{
@ -36,7 +39,7 @@ main.form>section>div>table label{
color:#e0e0e0;
}
main.form>section>div>table input{
main.form>section>div>table input[type="text"], main.form>section>div>table input[type="password"]{
display:block;
position:relative;
width:100%;
@ -81,3 +84,7 @@ main.form>section>div>button:disabled{
color:#606060;
cursor:pointer;
}
main.form>section>div input[type="checkbox"] {
height:1.5em;
}

View File

@ -12,30 +12,38 @@ let CONTEXT = {
};
const Status = {
Ok: 0x0000,
Error: 0x0001,
NotImplemented: 0x0002,
Ok :0x0000,
Error :0x0001,
NotImplemented :0x0002,
Approve :0x0003,
Reject :0x0004,
BadHandle: 0x0010,
BadSecret: 0x0011,
BadCode: 0x0012,
BadHandle :0x0010,
BadSecret :0x0011,
BadCode :0x0012,
};
const OpCode = {
Register :0x0010,
Authenticate :0x0011,
Resume :0x0012,
Deauthenticate :0x0013,
Register :0x0010,
Authenticate :0x0011,
Resume :0x0012,
Deauthenticate :0x0013,
SessionList :0x0020,
SessionCreate :0x0021,
SessionJoin :0x0022,
SessionRetire :0x002E,
SessionLeave :0x002F,
SessionList :0x0020,
//SessionJoin :0x0021,
SessionView :0x0022,
SessionRetire :0x002E,
SessionLeave :0x002F,
GameState :0x0030,
GamePlay :0x0031,
GameHistory :0x0032,
GameState :0x0030,
GamePlay :0x0031,
GameHistory :0x0032,
Challenge :0x0060,
ChallengeAnswer :0x0061,
ChallengeList :0x0062,
UserList :0x0100,
};
const GameState = {

View File

@ -798,8 +798,7 @@ const INTERFACE = {
switch(code) {
case OpCode.SessionCreate:
case OpCode.SessionJoin: {
case OpCode.SessionView: {
if(data.status != Status.Ok) {
LOAD(SCENES.Browse);
}

View File

@ -280,12 +280,6 @@ const SCENES = {
table.appendChild(UI.session_table(data.records));
}
} break;
case OpCode.SessionCreate:
case OpCode.SessionJoin: {
if(data.status == Status.Ok) {
LOAD(SCENES.Game, data);
}
} break;
}
},
disconnect() {
@ -293,7 +287,7 @@ const SCENES = {
},
},
Join:{
/*Join:{
load() {
if(sessionStorage.getItem("auth") === null) return false;
@ -337,8 +331,7 @@ const SCENES = {
table.appendChild(UI.session_table_join(data.records));
}
} break;
case OpCode.SessionCreate:
case OpCode.SessionJoin: {
case OpCode.SessionView: {
if(data.status == Status.Ok) {
LOAD(SCENES.Game, data);
}
@ -348,7 +341,7 @@ const SCENES = {
disconnect() {
LOAD(SCENES.Offline);
},
},
},*/
Live:{
load() {
@ -392,11 +385,6 @@ const SCENES = {
table.appendChild(UI.session_table(data.records));
}
} break;
{
if(data.status == Status.Ok) {
LOAD(SCENES.Game, data);
}
} break;
}
},
disconnect() {
@ -542,8 +530,7 @@ const SCENES = {
]
);
let slider = UI.slider((event) => { INTERFACE.replay_jump(event.target.value); });
slider.setAttribute("id", "turn-slider");
let slider = UI.slider("turn-slider", (event) => { INTERFACE.replay_jump(event.target.value); });
slider.setAttribute("min", "0");
slider.setAttribute("max", "0");
MAIN.appendChild(UI.div([ slider ], "turn-slider-padding"));
@ -564,7 +551,7 @@ const SCENES = {
message(code, data) {
switch(code) {
case OpCode.SessionCreate:
case OpCode.SessionJoin:
case OpCode.SessionView:
case OpCode.GameState:
case OpCode.GamePlay: {
INTERFACE.message(code, data);
@ -601,6 +588,216 @@ const SCENES = {
INTERFACE.uninit();
},
},
Await:{
load() {
if(sessionStorage.getItem("auth") === null) return false;
UI.mainmenu("await");
UI.mainnav([], [], { session:true });
let container = document.createElement("section");
let form = document.createElement("div");
form.appendChild(UI.text("Await a challenger:"));
form.appendChild(UI.table(null, [
[ UI.label("Accepting", "in-accept"), UI.checkbox("in-accept") ],
]));
let button = UI.button("Update", (event) => {
let accepting = 0;
if(document.getElementById("in-accept").value == "on") {
accepting = 1;
}
let flags = 0;
flags |= accepting;
MESSAGE_COMPOSE([
PACK.u16(OpCode.UserAwait),
PACK.u32(flags),
]);
LOAD_URL();
});
form.appendChild(button);
container.appendChild(form);
MAIN.appendChild(container);
MAIN.setAttribute("class", "form");
return true;
},
disconnect() {
LOAD(SCENES.Offline);
},
},
Challenge:{
load() {
CONTEXT.Data = {
page:0,
records:[],
};
UI.mainmenu("browse");
UI.mainnav(
[
UI.button("Users", () => { LOAD(SCENES.Challenge); }),
UI.button("Requests", () => { LOAD(SCENES.ChallengeList); }),
],
[
UI.div([UI.text("0 - 0 of 0")]),
UI.button("◀", null),
UI.button("▶", null),
UI.button("Refresh", null),
]
);
let table = document.createElement("table");
table.setAttribute("id", "content");
table.setAttribute("class", "list");
MAIN.appendChild(table);
SCENE.refresh();
history.pushState(null, "Omen", "/challenge/");
return true;
},
refresh() {
MESSAGE_COMPOSE([
PACK.u16(OpCode.UserList),
]);
},
message(code, data) {
switch(code) {
case OpCode.UserList: {
let table = document.getElementById("content");
UI.clear(table);
if(data !== null) {
let rows = [ ];
for(let r = 0; r < data.users.length; ++r) {
let buttons = [ ];
let callback = function() {
MESSAGE_CHALLENGE(this.handle);
};
callback = callback.bind({handle: data.users[r].handle});
buttons.push(UI.button("Challenge", callback));
rows.push([
UI.text(data.users[r].handle),
buttons,
]);
}
let tbody = UI.table_content(
[ "User", "" ],
rows,
);
table.appendChild(tbody);
}
} break;
}
},
disconnect() {
LOAD(SCENES.Offline);
},
},
ChallengeList:{
load() {
CONTEXT.Data = {
page:0,
records:[],
};
UI.mainmenu("browse");
UI.mainnav(
[
UI.button("Users", () => { LOAD(SCENES.Challenge); }),
UI.button("Requests", () => { LOAD(SCENES.ChallengeList); }),
],
[
UI.div([UI.text("0 - 0 of 0")]),
UI.button("◀", null),
UI.button("▶", null),
UI.button("Refresh", null),
]
);
let table = document.createElement("table");
table.setAttribute("id", "content");
table.setAttribute("class", "list");
MAIN.appendChild(table);
SCENE.refresh();
history.pushState(null, "Omen", "/challenge/");
return true;
},
refresh() {
MESSAGE_COMPOSE([
PACK.u16(OpCode.ChallengeList),
]);
},
message(code, data) {
switch(code) {
case OpCode.ChallengeList: {
let table = document.getElementById("content");
UI.clear(table);
if(data !== null) {
let rows = [ ];
for(let r = 0; r < data.challenges.length; ++r) {
let buttons = [ ];
let callback_accept = function() {
MESSAGE_CHALLENGE_ANSWER(this.handle, true);
};
callback_accept = callback_accept.bind({handle: data.challenges[r].handle});
buttons.push(UI.button("Accept", callback_accept));
let callback_reject = function() {
MESSAGE_CHALLENGE_ANSWER(this.handle, false);
};
callback_reject = callback_reject.bind({handle: data.challenges[r].handle});
buttons.push(UI.button("Reject", callback_reject));
rows.push([
UI.text(data.challenges[r].handle),
buttons,
]);
}
let tbody = UI.table_content(
[ "User", "" ],
rows,
);
table.appendChild(tbody);
}
} break;
case OpCode.ChallengeAnswer: {
if(data.status == Status.Ok) {
LOAD(SCENES.Game, {
token:data.token,
mode:INTERFACE.Mode.Player,
});
} else {
SCENE.refresh();
}
} break;
}
},
disconnect() {
LOAD(SCENES.Offline);
},
},
};
function LOAD(scene, data=null) {

View File

@ -181,8 +181,7 @@ function MESSAGE(event) {
}
} break;
case OpCode.SessionCreate:
case OpCode.SessionJoin: {
case OpCode.SessionView: {
console.log("RECV SessionCreate/Join");
if(bytes.length - index == 10) {
@ -278,6 +277,80 @@ function MESSAGE(event) {
data.play.to = (result.data >> 10) & 0x3F;
} break;
case OpCode.ChallengeAnswer: {
data = {
status:0,
token:new Uint8Array(8),
};
// Status
result = UNPACK.u16(bytes, index);
index = result.index;
data.status = result.data;
for(let i = 0; i < 8; ++i) { data.token[i] = bytes[index++]; }
} break;
case OpCode.ChallengeList: {
data = {
status:0,
challenges:[ ],
};
// Status
result = UNPACK.u16(bytes, index);
index = result.index;
data.status = result.data;
// Records
result = UNPACK.u16(bytes, index);
index = result.index;
let length = result.data;
for(let i = 0; i < length; ++i) {
let record = {
handle:"",
};
// Handle
result = UNPACK.string(bytes, index, UNPACK.u8);
index = result.index;
record.handle = result.data;
data.challenges.push(record);
}
} break;
case OpCode.UserList: {
data = {
status:0,
users:[ ],
};
// Status
result = UNPACK.u16(bytes, index);
index = result.index;
data.status = result.data;
// Records
result = UNPACK.u16(bytes, index);
index = result.index;
let length = result.data;
for(let i = 0; i < length; ++i) {
let record = {
handle:"",
};
// Handle
result = UNPACK.string(bytes, index, UNPACK.u8);
index = result.index;
record.handle = result.data;
data.users.push(record);
}
} break;
default:
console.log("RECV Undefined " + code);
return;
@ -318,16 +391,27 @@ function MESSAGE_SESSION_LIST(page, game_state, is_player, is_live) {
]);
}
function MESSAGE_SESSION_START() {
function MESSAGE_SESSION_VIEW(token, player) {
MESSAGE_COMPOSE([
PACK.u16(OpCode.SessionCreate),
]);
}
function MESSAGE_SESSION_JOIN(token, player) {
MESSAGE_COMPOSE([
PACK.u16(OpCode.SessionJoin),
PACK.u16(OpCode.SessionView),
token,
PACK.u8(player),
]);
}
function MESSAGE_CHALLENGE(handle) {
MESSAGE_COMPOSE([
PACK.u16(OpCode.Challenge),
PACK.string(handle, PACK.u8),
]);
}
function MESSAGE_CHALLENGE_ANSWER(handle, answer) {
console.log(handle + " " + answer);
MESSAGE_COMPOSE([
PACK.u16(OpCode.ChallengeAnswer),
PACK.u8(+answer),
PACK.string(handle, PACK.u8),
]);
}

View File

@ -11,14 +11,23 @@ const UI = {
return button;
},
slider(callback) {
let slider = document.createElement("input");
slider.setAttribute("type", "range");
slider.setAttribute("value", "0");
slider.setAttribute("min", "0");
slider.setAttribute("max", "0");
if(callback !== null) { slider.addEventListener("input", callback); }
return slider;
slider(id, callback) {
let input = document.createElement("input");
if(id !== null) { input.setAttribute("id", id); }
input.setAttribute("type", "range");
input.setAttribute("value", "0");
input.setAttribute("min", "0");
input.setAttribute("max", "0");
if(callback !== null) { input.addEventListener("input", callback); }
return input;
},
checkbox(id, callback) {
let input = document.createElement("input");
if(id !== null) { input.setAttribute("id", id); }
input.setAttribute("type", "checkbox");
if(callback !== null) { input.addEventListener("change", callback); }
return input;
},
textbox(id, placeholder) {
@ -101,8 +110,8 @@ const UI = {
}
} else {
if(features.session === true) {
left.appendChild(UI.button("Contest", () => { MESSAGE_SESSION_START(); }));
left.appendChild(UI.button("Challenge", () => { }));
//left.appendChild(UI.button("Await", () => { }));
left.appendChild(UI.button("Challenge", () => { LOAD(SCENES.Challenge); }));
}
}
@ -135,7 +144,7 @@ const UI = {
top.push(UI.button("Browse", () => { LOAD(SCENES.Browse); }, page == "browse"));
if(sessionStorage.getItem("auth") !== null) {
top.push(UI.button("Continue", () => { LOAD(SCENES.Continue); }, page == "continue"));
top.push(UI.button("Join", () => { LOAD(SCENES.Join); }, page == "join"));
//top.push(UI.button("Join", () => { LOAD(SCENES.Join); }, page == "join"));
}
top.push(UI.button("Live", () => { LOAD(SCENES.Live); }, page == "live"));
top.push(UI.button("History", () => { LOAD(SCENES.History); }, page == "history"));
@ -167,7 +176,7 @@ const UI = {
token:this.token,
mode:INTERFACE.Mode.Player,
});
MESSAGE_SESSION_JOIN(this.token, true);
MESSAGE_SESSION_VIEW(this.token, true);
};
join_callback = join_callback.bind({token: records[r].token});
@ -176,7 +185,7 @@ const UI = {
token:this.token,
mode:INTERFACE.Mode.Review,
});
MESSAGE_SESSION_JOIN(this.token, false);
MESSAGE_SESSION_VIEW(this.token, false);
};
spectate_callback = spectate_callback.bind({token: records[r].token});
@ -223,7 +232,7 @@ const UI = {
token:this.token,
mode:INTERFACE.Mode.Player,
});
MESSAGE_SESSION_JOIN(this.token, true);
MESSAGE_SESSION_VIEW(this.token, true);
};
join_callback = join_callback.bind({token: records[r].token});
@ -260,7 +269,7 @@ const UI = {
token:this.token,
mode:INTERFACE.Mode.Review,
});
MESSAGE_SESSION_JOIN(this.token, false);
MESSAGE_SESSION_VIEW(this.token, false);
};
view_callback = view_callback.bind({token: records[r].token});
@ -289,7 +298,7 @@ const UI = {
},
clear(dom) {
while(dom.lastChild !== null) { dom.removeChild(document.body.lastChild); }
while(dom.lastChild !== null) { dom.removeChild(dom.lastChild); }
},
rebuild() {

View File

@ -13,6 +13,19 @@ const PACK = {
value & 0xFF
]);
},
string(text, pack) {
let enc = new TextEncoder();
let bytes = enc.encode(text);
let size_bytes = pack(bytes.length);
let length = size_bytes.length + bytes.length;
let result = new Uint8Array(length);
result.set(size_bytes, 0);
result.set(bytes, size_bytes.length);
return result;
},
base64(bytes) {
let str = "";
for(let i = 0; i < bytes.length; ++i) {
@ -50,8 +63,8 @@ const UNPACK = {
}
return { data: result, index: index };
},
string(data, index) {
let result = UNPACK.u16(data, index);
string(data, index, unpack=UNPACK.u16) {
let result = unpack(data, index);
index = result.index;
let length = result.data;
@ -66,7 +79,7 @@ const UNPACK = {
index += length;
result_str = dec.decode(bytes);
} else { console.log("INV DATA LEN"); }
} else { console.log("error: unexpected end of data (" + data.length + " / " + length + ")"); }
return { data: result_str, index: index };
},