Add URL and authentication persistence.
This commit is contained in:
parent
1df7dab092
commit
a90ace5af9
@ -28,6 +28,8 @@ pub struct App {
|
|||||||
pub auths:Trie<Authentication>,
|
pub auths:Trie<Authentication>,
|
||||||
pub sessions:Trie<Session>,
|
pub sessions:Trie<Session>,
|
||||||
|
|
||||||
|
pub contests:Vec<u32>,
|
||||||
|
|
||||||
pub session_time:Chain<SessionToken>,
|
pub session_time:Chain<SessionToken>,
|
||||||
}
|
}
|
||||||
impl App {
|
impl App {
|
||||||
@ -35,6 +37,8 @@ impl App {
|
|||||||
{
|
{
|
||||||
if let Ok(mut filesystem) = FileSystem::init() {
|
if let Ok(mut filesystem) = FileSystem::init() {
|
||||||
|
|
||||||
|
let mut contests = Vec::new();
|
||||||
|
|
||||||
// Load salts
|
// Load salts
|
||||||
println!("Loading salts..");
|
println!("Loading salts..");
|
||||||
let mut salts = Sparse::new();
|
let mut salts = Sparse::new();
|
||||||
@ -60,6 +64,15 @@ impl App {
|
|||||||
let user_count = filesystem.user_count()?;
|
let user_count = filesystem.user_count()?;
|
||||||
for id in 0..user_count {
|
for id in 0..user_count {
|
||||||
let user = filesystem.user_fetch(id as u32).unwrap();
|
let user = filesystem.user_fetch(id as u32).unwrap();
|
||||||
|
|
||||||
|
// Add user to contests if flag is set.
|
||||||
|
if (user.flags & user::F_CONTEST) != 0 {
|
||||||
|
match contests.binary_search(&user.id) {
|
||||||
|
Ok(_) => { }
|
||||||
|
Err(pos) => { contests.insert(pos, user.id); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let user_local_id = users.add(user);
|
let user_local_id = users.add(user);
|
||||||
user_id.set(user_local_id as isize, id);
|
user_id.set(user_local_id as isize, id);
|
||||||
}
|
}
|
||||||
@ -106,6 +119,8 @@ impl App {
|
|||||||
auths:Trie::new(),
|
auths:Trie::new(),
|
||||||
sessions,
|
sessions,
|
||||||
|
|
||||||
|
contests,
|
||||||
|
|
||||||
session_time,
|
session_time,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
pub const F_CONTEST :u32 = 0x0000_0001;
|
||||||
|
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id:u32,
|
pub id:u32,
|
||||||
pub handle_id:u32,
|
pub flags:u32,
|
||||||
pub handle:String,
|
pub handle:String,
|
||||||
pub secret:Vec<u8>,
|
pub secret:Vec<u8>,
|
||||||
pub na_key:u32,
|
pub na_key:u32,
|
||||||
|
@ -53,21 +53,15 @@ async fn service_http(mut request:hyper::Request<hyper::body::Incoming>, args:Ht
|
|||||||
.unwrap())
|
.unwrap())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match request.uri().path() {
|
match args.cache.fetch(request.uri().path()) {
|
||||||
"/" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "text/html")
|
|
||||||
.header(CACHE_CONTROL, "no-cache")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.fetch("/.html").unwrap().data))).unwrap()),
|
|
||||||
|
|
||||||
_ => match args.cache.fetch(request.uri().path()) {
|
|
||||||
Some(data) => Ok(Response::builder()
|
Some(data) => Ok(Response::builder()
|
||||||
.header(CONTENT_TYPE, &data.mime)
|
.header(CONTENT_TYPE, &data.mime)
|
||||||
.header(CACHE_CONTROL, "no-cache")
|
.header(CACHE_CONTROL, "no-cache")
|
||||||
.body(Full::new(Bytes::from(data.data))).unwrap()),
|
.body(Full::new(Bytes::from(data.data))).unwrap()),
|
||||||
None => Ok(Response::builder()
|
None => Ok(Response::builder()
|
||||||
.status(404)
|
.header(CONTENT_TYPE, "text/html")
|
||||||
.body(Full::new(Bytes::new())).unwrap())
|
.header(CACHE_CONTROL, "no-cache")
|
||||||
}
|
.body(Full::new(Bytes::from(args.cache.fetch("/.html").unwrap().data))).unwrap()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,12 +105,12 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
let user_id = app.filesystem.user_count().unwrap() as u32;
|
let user_id = app.filesystem.user_count().unwrap() as u32;
|
||||||
|
|
||||||
// Register user pool id and handle
|
// Register user pool id and handle
|
||||||
let handle_id = app.filesystem.handle_store(&handle, user_id).unwrap();
|
app.filesystem.handle_store(&handle, user_id).ok();
|
||||||
app.user_handle.set(handle.as_bytes(), user_id);
|
app.user_handle.set(handle.as_bytes(), user_id);
|
||||||
|
|
||||||
let user_data = User {
|
let user_data = User {
|
||||||
id:user_id,
|
id:user_id,
|
||||||
handle_id,
|
flags:0,
|
||||||
handle:display_name,
|
handle:display_name,
|
||||||
secret,
|
secret,
|
||||||
na_key:salt_id,
|
na_key:salt_id,
|
||||||
@ -225,6 +225,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
QRPacketData::QAuthResume(request) => {
|
QRPacketData::QAuthResume(request) => {
|
||||||
|
println!("Request: Auth Resume");
|
||||||
|
|
||||||
let mut response = PacketAuthResumeResponse::new();
|
let mut response = PacketAuthResumeResponse::new();
|
||||||
response.status = STATUS_ERROR;
|
response.status = STATUS_ERROR;
|
||||||
|
|
||||||
@ -250,6 +252,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
QRPacketData::QAuthRevoke => {
|
QRPacketData::QAuthRevoke => {
|
||||||
|
println!("Request: Auth Revoke");
|
||||||
|
|
||||||
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) => {
|
||||||
@ -692,6 +696,8 @@ fn generate_game_history(app:&App, session:&Session) -> protocol::PacketGameHist
|
|||||||
|
|
||||||
let mut response = PacketGameHistoryResponse::new();
|
let mut response = PacketGameHistoryResponse::new();
|
||||||
|
|
||||||
|
response.token = session.token;
|
||||||
|
|
||||||
// Get Dawn handle
|
// Get Dawn handle
|
||||||
if let Some(id) = session.p_dawn.user {
|
if let Some(id) = session.p_dawn.user {
|
||||||
if let Some(user) = app.get_user_by_id(id) {
|
if let Some(user) = app.get_user_by_id(id) {
|
||||||
|
@ -64,7 +64,7 @@ pub async fn handle_ws(ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServiceAr
|
|||||||
QRPacket::new(conn_id, QRPacketData::QRegister(packet))
|
QRPacket::new(conn_id, QRPacketData::QRegister(packet))
|
||||||
).ok();
|
).ok();
|
||||||
}
|
}
|
||||||
Err(_) => { }
|
Err(_) => { println!("error: packet decode failed."); }
|
||||||
}
|
}
|
||||||
|
|
||||||
CODE_AUTH => match PacketAuth::decode(&data, &mut index) {
|
CODE_AUTH => match PacketAuth::decode(&data, &mut index) {
|
||||||
@ -74,7 +74,7 @@ pub async fn handle_ws(ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServiceAr
|
|||||||
QRPacket::new(conn_id, QRPacketData::QAuth(packet))
|
QRPacket::new(conn_id, QRPacketData::QAuth(packet))
|
||||||
).ok();
|
).ok();
|
||||||
}
|
}
|
||||||
Err(_) => { }
|
Err(_) => { println!("error: packet decode failed."); }
|
||||||
}
|
}
|
||||||
|
|
||||||
CODE_AUTH_RESUME => match PacketAuthResume::decode(&data, &mut index) {
|
CODE_AUTH_RESUME => match PacketAuthResume::decode(&data, &mut index) {
|
||||||
@ -84,7 +84,7 @@ pub async fn handle_ws(ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServiceAr
|
|||||||
QRPacket::new(conn_id, QRPacketData::QAuthResume(packet))
|
QRPacket::new(conn_id, QRPacketData::QAuthResume(packet))
|
||||||
).ok();
|
).ok();
|
||||||
}
|
}
|
||||||
Err(_) => { }
|
Err(_) => { println!("error: packet decode failed."); }
|
||||||
}
|
}
|
||||||
|
|
||||||
CODE_AUTH_REVOKE => {
|
CODE_AUTH_REVOKE => {
|
||||||
|
@ -41,6 +41,7 @@ impl Packet for PacketGameHistory {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PacketGameHistoryResponse {
|
pub struct PacketGameHistoryResponse {
|
||||||
pub status:u16,
|
pub status:u16,
|
||||||
|
pub token:SessionToken,
|
||||||
pub dawn_handle:String,
|
pub dawn_handle:String,
|
||||||
pub dusk_handle:String,
|
pub dusk_handle:String,
|
||||||
pub history:Vec<Play>,
|
pub history:Vec<Play>,
|
||||||
@ -50,6 +51,7 @@ impl PacketGameHistoryResponse {
|
|||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
status:0,
|
status:0,
|
||||||
|
token:SessionToken::default(),
|
||||||
dawn_handle:String::new(),
|
dawn_handle:String::new(),
|
||||||
dusk_handle:String::new(),
|
dusk_handle:String::new(),
|
||||||
history:Vec::new(),
|
history:Vec::new(),
|
||||||
@ -72,6 +74,7 @@ impl Packet for PacketGameHistoryResponse {
|
|||||||
|
|
||||||
[
|
[
|
||||||
pack_u16(self.status),
|
pack_u16(self.status),
|
||||||
|
self.token.to_vec(),
|
||||||
pack_u16(self.dawn_handle.len() as u16),
|
pack_u16(self.dawn_handle.len() as u16),
|
||||||
self.dawn_handle.as_bytes().to_vec(),
|
self.dawn_handle.as_bytes().to_vec(),
|
||||||
pack_u16(self.dusk_handle.len() as u16),
|
pack_u16(self.dusk_handle.len() as u16),
|
||||||
|
@ -308,7 +308,7 @@ impl FileSystem {
|
|||||||
let handle = user.handle.as_bytes().to_vec();
|
let handle = user.handle.as_bytes().to_vec();
|
||||||
|
|
||||||
// Write user information
|
// Write user information
|
||||||
file.write(&pack_u32(user.handle_id)).map_err(|_| ())?;
|
file.write(&pack_u32(user.flags)).map_err(|_| ())?;
|
||||||
file.write(&pack_u32(user.na_key)).map_err(|_| ())?;
|
file.write(&pack_u32(user.na_key)).map_err(|_| ())?;
|
||||||
file.write(&pack_u16(user.secret.len() as u16)).map_err(|_| ())?;
|
file.write(&pack_u16(user.secret.len() as u16)).map_err(|_| ())?;
|
||||||
file.write(&user.secret).map_err(|_| ())?;
|
file.write(&user.secret).map_err(|_| ())?;
|
||||||
@ -340,7 +340,7 @@ impl FileSystem {
|
|||||||
let mut buffer_u32 = [0u8; 4];
|
let mut buffer_u32 = [0u8; 4];
|
||||||
|
|
||||||
file.read_exact(&mut buffer_u32).map_err(|_| ())?;
|
file.read_exact(&mut buffer_u32).map_err(|_| ())?;
|
||||||
let handle_id = unpack_u32(&buffer_u32, &mut 0);
|
let flags = unpack_u32(&buffer_u32, &mut 0);
|
||||||
|
|
||||||
file.read_exact(&mut buffer_u32).map_err(|_| ())?;
|
file.read_exact(&mut buffer_u32).map_err(|_| ())?;
|
||||||
let na_key = unpack_u32(&buffer_u32, &mut 0);
|
let na_key = unpack_u32(&buffer_u32, &mut 0);
|
||||||
@ -361,7 +361,7 @@ impl FileSystem {
|
|||||||
|
|
||||||
Ok(User {
|
Ok(User {
|
||||||
id,
|
id,
|
||||||
handle_id,
|
flags,
|
||||||
handle,
|
handle,
|
||||||
secret,
|
secret,
|
||||||
na_key,
|
na_key,
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
<meta name="application-name" content="Omen">
|
<meta name="application-name" content="Omen">
|
||||||
<meta name="description" content="Strategy board game played on a hexagon grid.">
|
<meta name="description" content="Strategy board game played on a hexagon grid.">
|
||||||
<link rel="icon" href="/favicon.png">
|
<link rel="icon" href="/favicon.png">
|
||||||
<link rel="stylesheet" href=".css">
|
<link rel="stylesheet" href="/.css">
|
||||||
<script src=".js"></script>
|
<script src="/.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>This application requires JavaScript to function.</noscript>
|
<noscript>This application requires JavaScript to function.</noscript>
|
||||||
|
@ -7,15 +7,15 @@ GAME_ASSET.load_image = (image) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
GAME_ASSET.Image = {
|
GAME_ASSET.Image = {
|
||||||
Promote: GAME_ASSET.load_image("asset/promote.svg"),
|
Promote: GAME_ASSET.load_image("/asset/promote.svg"),
|
||||||
Piece: [
|
Piece: [
|
||||||
[ GAME_ASSET.load_image("asset/militia_dawn.svg"), GAME_ASSET.load_image("asset/militia_dusk.svg") ],
|
[ GAME_ASSET.load_image("/asset/militia_dawn.svg"), GAME_ASSET.load_image("/asset/militia_dusk.svg") ],
|
||||||
[ GAME_ASSET.load_image("asset/lance_dawn.svg"), GAME_ASSET.load_image("asset/lance_dusk.svg") ],
|
[ GAME_ASSET.load_image("/asset/lance_dawn.svg"), GAME_ASSET.load_image("/asset/lance_dusk.svg") ],
|
||||||
[ GAME_ASSET.load_image("asset/knight_dawn.svg"), GAME_ASSET.load_image("asset/knight_dusk.svg") ],
|
[ GAME_ASSET.load_image("/asset/knight_dawn.svg"), GAME_ASSET.load_image("/asset/knight_dusk.svg") ],
|
||||||
[ GAME_ASSET.load_image("asset/tower_dawn.svg"), GAME_ASSET.load_image("asset/tower_dusk.svg") ],
|
[ GAME_ASSET.load_image("/asset/tower_dawn.svg"), GAME_ASSET.load_image("/asset/tower_dusk.svg") ],
|
||||||
[ GAME_ASSET.load_image("asset/castle_dawn.svg"), GAME_ASSET.load_image("asset/castle_dusk.svg") ],
|
[ GAME_ASSET.load_image("/asset/castle_dawn.svg"), GAME_ASSET.load_image("/asset/castle_dusk.svg") ],
|
||||||
[ GAME_ASSET.load_image("asset/dragon_dawn.svg"), GAME_ASSET.load_image("asset/dragon_dusk.svg") ],
|
[ GAME_ASSET.load_image("/asset/dragon_dawn.svg"), GAME_ASSET.load_image("/asset/dragon_dusk.svg") ],
|
||||||
[ GAME_ASSET.load_image("asset/behemoth_dawn.svg"), GAME_ASSET.load_image("asset/behemoth_dusk.svg") ],
|
[ GAME_ASSET.load_image("/asset/behemoth_dawn.svg"), GAME_ASSET.load_image("/asset/behemoth_dusk.svg") ],
|
||||||
[ GAME_ASSET.load_image("asset/omen_dawn.svg"), GAME_ASSET.load_image("asset/omen_dusk.svg") ],
|
[ GAME_ASSET.load_image("/asset/omen_dawn.svg"), GAME_ASSET.load_image("/asset/omen_dusk.svg") ],
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
145
www/js/scene.js
145
www/js/scene.js
@ -1,8 +1,7 @@
|
|||||||
const SCENES = {
|
const SCENES = {
|
||||||
Init:{
|
Init:{
|
||||||
load() {
|
load() {
|
||||||
LOAD_STACK(SCENES.Offline);
|
LOAD(SCENES.Offline);
|
||||||
CONTEXT.Scene = SCENES.Browse;
|
|
||||||
RECONNECT();
|
RECONNECT();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@ -11,20 +10,20 @@ const SCENES = {
|
|||||||
Offline:{
|
Offline:{
|
||||||
load() {
|
load() {
|
||||||
UI.nav([
|
UI.nav([
|
||||||
UI.button("Reconnect", () => { RECONNECT(); })
|
UI.button("Reconnect", () => { RECONNECT(); }),
|
||||||
], []);
|
], []);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
reconnect() {
|
reconnect() {
|
||||||
LOAD(CONTEXT.Scene);
|
LOAD_URL();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Register:{
|
Register:{
|
||||||
load() {
|
load() {
|
||||||
if(CONTEXT.Auth !== null) return false;
|
if(sessionStorage.getItem("auth") !== null) return false;
|
||||||
UI.mainmenu("register");
|
UI.mainmenu("register");
|
||||||
UI.mainnav([], []);
|
UI.mainnav([], [], { auth:true });
|
||||||
|
|
||||||
let container = document.createElement("section");
|
let container = document.createElement("section");
|
||||||
let form = document.createElement("div");
|
let form = document.createElement("div");
|
||||||
@ -81,8 +80,14 @@ const SCENES = {
|
|||||||
let submit = document.getElementById("submit");
|
let submit = document.getElementById("submit");
|
||||||
switch(data.status) {
|
switch(data.status) {
|
||||||
case Status.Ok: {
|
case Status.Ok: {
|
||||||
CONTEXT.Auth = data;
|
let b64_token = PACK.base64(data.token);
|
||||||
LOAD(SCENES.Browse);
|
let b64_secret = PACK.base64(data.secret);
|
||||||
|
console.log(b64_token);
|
||||||
|
console.log(b64_secret);
|
||||||
|
|
||||||
|
sessionStorage.setItem("auth", b64_token);
|
||||||
|
sessionStorage.setItem("auth_secret", b64_secret);
|
||||||
|
LOAD_URL();
|
||||||
} break;
|
} break;
|
||||||
default: {
|
default: {
|
||||||
submit.removeAttribute("disabled");
|
submit.removeAttribute("disabled");
|
||||||
@ -105,15 +110,15 @@ const SCENES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
disconnect() {
|
disconnect() {
|
||||||
LOAD_STACK(SCENES.Offline);
|
LOAD(SCENES.Offline);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Authenticate:{
|
Authenticate:{
|
||||||
load() {
|
load() {
|
||||||
if(CONTEXT.Auth !== null) return false;
|
if(sessionStorage.getItem("auth") !== null) return false;
|
||||||
UI.mainmenu("authenticate");
|
UI.mainmenu("authenticate");
|
||||||
UI.mainnav([], []);
|
UI.mainnav([], [], { auth:true });
|
||||||
|
|
||||||
let container = document.createElement("section");
|
let container = document.createElement("section");
|
||||||
let form = document.createElement("div");
|
let form = document.createElement("div");
|
||||||
@ -163,8 +168,9 @@ const SCENES = {
|
|||||||
let submit = document.getElementById("submit");
|
let submit = document.getElementById("submit");
|
||||||
switch(data.status) {
|
switch(data.status) {
|
||||||
case Status.Ok: {
|
case Status.Ok: {
|
||||||
CONTEXT.Auth = data;
|
sessionStorage.setItem("auth", PACK.base64(data.token));
|
||||||
LOAD(SCENES.Browse);
|
sessionStorage.setItem("auth_secret", PACK.base64(data.secret));
|
||||||
|
LOAD_URL();
|
||||||
} break;
|
} break;
|
||||||
case Status.Error: {
|
case Status.Error: {
|
||||||
submit.removeAttribute("disabled");
|
submit.removeAttribute("disabled");
|
||||||
@ -177,7 +183,7 @@ const SCENES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
disconnect() {
|
disconnect() {
|
||||||
LOAD_STACK(SCENES.Offline);
|
LOAD(SCENES.Offline);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -189,14 +195,15 @@ const SCENES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
UI.mainmenu("browse");
|
UI.mainmenu("browse");
|
||||||
UI.mainnav_std(
|
UI.mainnav(
|
||||||
[ ],
|
[ ],
|
||||||
[
|
[
|
||||||
UI.div([UI.text("0 - 0 of 0")]),
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
UI.button("◀", null),
|
UI.button("◀", null),
|
||||||
UI.button("▶", null),
|
UI.button("▶", null),
|
||||||
UI.button("Refresh", null),
|
UI.button("Refresh", null),
|
||||||
]
|
],
|
||||||
|
{ auth:true, session:true }
|
||||||
);
|
);
|
||||||
|
|
||||||
let table = document.createElement("table");
|
let table = document.createElement("table");
|
||||||
@ -205,6 +212,8 @@ const SCENES = {
|
|||||||
MAIN.appendChild(table);
|
MAIN.appendChild(table);
|
||||||
|
|
||||||
SCENE.refresh();
|
SCENE.refresh();
|
||||||
|
|
||||||
|
history.pushState(null, "Omen", "/");
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh() {
|
refresh() {
|
||||||
@ -229,13 +238,13 @@ const SCENES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
disconnect() {
|
disconnect() {
|
||||||
LOAD_STACK(SCENES.Offline);
|
LOAD(SCENES.Offline);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Continue:{
|
Continue:{
|
||||||
load() {
|
load() {
|
||||||
if(CONTEXT.Auth === null) return false;
|
if(sessionStorage.getItem("auth") === null) return false;
|
||||||
|
|
||||||
CONTEXT.Data = {
|
CONTEXT.Data = {
|
||||||
page:0,
|
page:0,
|
||||||
@ -243,14 +252,15 @@ const SCENES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
UI.mainmenu("continue");
|
UI.mainmenu("continue");
|
||||||
UI.mainnav_std(
|
UI.mainnav(
|
||||||
[ ],
|
[ ],
|
||||||
[
|
[
|
||||||
UI.div([UI.text("0 - 0 of 0")]),
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
UI.button("◀", null),
|
UI.button("◀", null),
|
||||||
UI.button("▶", null),
|
UI.button("▶", null),
|
||||||
UI.button("Refresh", null),
|
UI.button("Refresh", null),
|
||||||
]
|
],
|
||||||
|
{ auth:true, session:true }
|
||||||
);
|
);
|
||||||
|
|
||||||
let table = document.createElement("table");
|
let table = document.createElement("table");
|
||||||
@ -259,6 +269,8 @@ const SCENES = {
|
|||||||
MAIN.appendChild(table);
|
MAIN.appendChild(table);
|
||||||
|
|
||||||
SCENE.refresh();
|
SCENE.refresh();
|
||||||
|
|
||||||
|
history.pushState(null, "Omen - Continue", "/continue/");
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh() {
|
refresh() {
|
||||||
@ -283,13 +295,13 @@ const SCENES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
disconnect() {
|
disconnect() {
|
||||||
LOAD_STACK(SCENES.Offline);
|
LOAD(SCENES.Offline);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Join:{
|
Join:{
|
||||||
load() {
|
load() {
|
||||||
if(CONTEXT.Auth === null) return false;
|
if(sessionStorage.getItem("auth") === null) return false;
|
||||||
|
|
||||||
CONTEXT.Data = {
|
CONTEXT.Data = {
|
||||||
page:0,
|
page:0,
|
||||||
@ -297,14 +309,15 @@ const SCENES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
UI.mainmenu("join");
|
UI.mainmenu("join");
|
||||||
UI.mainnav_std(
|
UI.mainnav(
|
||||||
[ ],
|
[ ],
|
||||||
[
|
[
|
||||||
UI.div([UI.text("0 - 0 of 0")]),
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
UI.button("◀", null),
|
UI.button("◀", null),
|
||||||
UI.button("▶", null),
|
UI.button("▶", null),
|
||||||
UI.button("Refresh", null),
|
UI.button("Refresh", null),
|
||||||
]
|
],
|
||||||
|
{ auth:true, session:true }
|
||||||
);
|
);
|
||||||
|
|
||||||
let table = document.createElement("table");
|
let table = document.createElement("table");
|
||||||
@ -313,6 +326,8 @@ const SCENES = {
|
|||||||
MAIN.appendChild(table);
|
MAIN.appendChild(table);
|
||||||
|
|
||||||
SCENE.refresh();
|
SCENE.refresh();
|
||||||
|
|
||||||
|
history.pushState(null, "Omen - Join", "/join/");
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh() {
|
refresh() {
|
||||||
@ -337,7 +352,7 @@ const SCENES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
disconnect() {
|
disconnect() {
|
||||||
LOAD_STACK(SCENES.Offline);
|
LOAD(SCENES.Offline);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -349,14 +364,15 @@ const SCENES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
UI.mainmenu("live");
|
UI.mainmenu("live");
|
||||||
UI.mainnav_std(
|
UI.mainnav(
|
||||||
[ ],
|
[ ],
|
||||||
[
|
[
|
||||||
UI.div([UI.text("0 - 0 of 0")]),
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
UI.button("◀", null),
|
UI.button("◀", null),
|
||||||
UI.button("▶", null),
|
UI.button("▶", null),
|
||||||
UI.button("Refresh", null),
|
UI.button("Refresh", null),
|
||||||
]
|
],
|
||||||
|
{ auth:true, session:true }
|
||||||
);
|
);
|
||||||
|
|
||||||
let table = document.createElement("table");
|
let table = document.createElement("table");
|
||||||
@ -365,6 +381,8 @@ const SCENES = {
|
|||||||
MAIN.appendChild(table);
|
MAIN.appendChild(table);
|
||||||
|
|
||||||
SCENE.refresh();
|
SCENE.refresh();
|
||||||
|
|
||||||
|
history.pushState(null, "Omen - Live", "/live/");
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh() {
|
refresh() {
|
||||||
@ -389,7 +407,7 @@ const SCENES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
disconnect() {
|
disconnect() {
|
||||||
LOAD_STACK(SCENES.Offline);
|
LOAD(SCENES.Offline);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -401,14 +419,15 @@ const SCENES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
UI.mainmenu("history");
|
UI.mainmenu("history");
|
||||||
UI.mainnav_std(
|
UI.mainnav(
|
||||||
[ ],
|
[ ],
|
||||||
[
|
[
|
||||||
UI.div([UI.text("0 - 0 of 0")]),
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
UI.button("◀", null),
|
UI.button("◀", null),
|
||||||
UI.button("▶", null),
|
UI.button("▶", null),
|
||||||
UI.button("Refresh", null),
|
UI.button("Refresh", null),
|
||||||
]
|
],
|
||||||
|
{ auth:true }
|
||||||
);
|
);
|
||||||
|
|
||||||
let table = document.createElement("table");
|
let table = document.createElement("table");
|
||||||
@ -417,6 +436,8 @@ const SCENES = {
|
|||||||
MAIN.appendChild(table);
|
MAIN.appendChild(table);
|
||||||
|
|
||||||
SCENE.refresh();
|
SCENE.refresh();
|
||||||
|
|
||||||
|
history.pushState(null, "Omen - History", "/history/");
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh() {
|
refresh() {
|
||||||
@ -440,7 +461,7 @@ const SCENES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
disconnect() {
|
disconnect() {
|
||||||
LOAD_STACK(SCENES.Offline);
|
LOAD(SCENES.Offline);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -458,6 +479,8 @@ const SCENES = {
|
|||||||
MAIN.appendChild(body);
|
MAIN.appendChild(body);
|
||||||
|
|
||||||
this.refresh("game.html");
|
this.refresh("game.html");
|
||||||
|
|
||||||
|
history.pushState(null, "Omen - Guide", "/guide/");
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh(page) {
|
refresh(page) {
|
||||||
@ -483,6 +506,8 @@ const SCENES = {
|
|||||||
MAIN.appendChild(body);
|
MAIN.appendChild(body);
|
||||||
|
|
||||||
this.refresh("main.html");
|
this.refresh("main.html");
|
||||||
|
|
||||||
|
history.pushState(null, "Omen - About", "/about/");
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh(page) {
|
refresh(page) {
|
||||||
@ -521,6 +546,7 @@ const SCENES = {
|
|||||||
|
|
||||||
INTERFACE.init(data, INTERFACE.Mode.Online);
|
INTERFACE.init(data, INTERFACE.Mode.Online);
|
||||||
|
|
||||||
|
history.pushState(null, "Omen - Game", "/game/" + PACK.base64(data.token).slice(0, -1));
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
unload() {
|
unload() {
|
||||||
@ -541,7 +567,7 @@ const SCENES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
disconnect() {
|
disconnect() {
|
||||||
LOAD_STACK(SCENES.Offline);
|
LOAD(SCENES.Offline);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -563,6 +589,7 @@ const SCENES = {
|
|||||||
|
|
||||||
INTERFACE.init(data, INTERFACE.Mode.Local);
|
INTERFACE.init(data, INTERFACE.Mode.Local);
|
||||||
|
|
||||||
|
history.pushState(null, "Omen - Practice", "/practice/");
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
unload() {
|
unload() {
|
||||||
@ -573,7 +600,7 @@ const SCENES = {
|
|||||||
GameHistory:{
|
GameHistory:{
|
||||||
load(data) {
|
load(data) {
|
||||||
let buttons_bottom = [ ];
|
let buttons_bottom = [ ];
|
||||||
buttons_bottom.push(UI.button("Back", () => { LOAD(SCENES.Browse) }));
|
buttons_bottom.push(UI.button("Back", () => { LOAD(SCENES.History) }));
|
||||||
|
|
||||||
UI.nav([
|
UI.nav([
|
||||||
UI.button("Rotate", () => { INTERFACE.rotate(); }),
|
UI.button("Rotate", () => { INTERFACE.rotate(); }),
|
||||||
@ -607,6 +634,7 @@ const SCENES = {
|
|||||||
|
|
||||||
INTERFACE.init(data, INTERFACE.Mode.Replay);
|
INTERFACE.init(data, INTERFACE.Mode.Replay);
|
||||||
|
|
||||||
|
history.pushState(null, "Omen - History", "/history/" + PACK.base64(data.token).slice(0, -1));
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
unload() {
|
unload() {
|
||||||
@ -619,8 +647,6 @@ function LOAD(scene, data=null) {
|
|||||||
UNLOAD();
|
UNLOAD();
|
||||||
UI.rebuild();
|
UI.rebuild();
|
||||||
SCENE = scene;
|
SCENE = scene;
|
||||||
CONTEXT.Scene = SCENE;
|
|
||||||
CONTEXT.Data = null;
|
|
||||||
if(!SCENE.load(data)) { LOAD(SCENES.Browse); }
|
if(!SCENE.load(data)) { LOAD(SCENES.Browse); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -628,10 +654,47 @@ function UNLOAD() {
|
|||||||
if(SCENE !== null && SCENE.unload !== undefined) { SCENE.unload(); }
|
if(SCENE !== null && SCENE.unload !== undefined) { SCENE.unload(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
function LOAD_STACK(scene, data=null) {
|
function LOAD_URL() {
|
||||||
UNLOAD();
|
let parts = window.location.pathname.split("/");
|
||||||
UI.rebuild();
|
|
||||||
SCENE = scene;
|
if(parts.length > 1) {
|
||||||
CONTEXT.Data = null;
|
switch(parts[1]) {
|
||||||
if(!SCENE.load(data)) { LOAD(SCENES.Browse); }
|
case "continue": LOAD(SCENES.Continue); break;
|
||||||
|
case "live": LOAD(SCENES.Live); break;
|
||||||
|
case "practice": LOAD(SCENES.GamePractice); break;
|
||||||
|
case "guide": LOAD(SCENES.Guide); break;
|
||||||
|
case "practice": LOAD(SCENES.About); break;
|
||||||
|
|
||||||
|
case "history": {
|
||||||
|
LOAD(SCENES.History);
|
||||||
|
if(parts[2]) {
|
||||||
|
let token = UNPACK.base64(parts[2] + "=");
|
||||||
|
MESSAGE_COMPOSE([
|
||||||
|
PACK.u16(OpCode.GameHistory),
|
||||||
|
token,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case "join": {
|
||||||
|
LOAD(SCENES.Join);
|
||||||
|
if(parts[2]) {
|
||||||
|
let token = UNPACK.base64(parts[2] + "=");
|
||||||
|
MESSAGE_SESSION_JOIN(token, true);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case "game": {
|
||||||
|
LOAD(SCENES.Browse);
|
||||||
|
if(parts[2]) {
|
||||||
|
let token = UNPACK.base64(parts[2] + "=");
|
||||||
|
MESSAGE_SESSION_JOIN(token, false);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default: LOAD(SCENES.Browse);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOAD(SCENES.Browse);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,10 +29,16 @@ function RECONNECT() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function RESUME() {
|
function RESUME() {
|
||||||
if(CONTEXT.Auth !== null) {
|
//sessionStorage.clear();
|
||||||
|
let b64_auth = sessionStorage.getItem("auth");
|
||||||
|
if(b64_auth !== null) {
|
||||||
|
let auth = UNPACK.base64(b64_auth);
|
||||||
|
let secret = UNPACK.base64(sessionStorage.getItem("auth_secret"));
|
||||||
|
|
||||||
MESSAGE_COMPOSE([
|
MESSAGE_COMPOSE([
|
||||||
CONTEXT.Auth.token,
|
PACK.u16(OpCode.Resume),
|
||||||
CONTEXT.Auth.secret,
|
auth,
|
||||||
|
secret,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
if(SCENE.reconnect !== undefined) { SCENE.reconnect(); }
|
if(SCENE.reconnect !== undefined) { SCENE.reconnect(); }
|
||||||
@ -40,7 +46,6 @@ function RESUME() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function MESSAGE(event) {
|
function MESSAGE(event) {
|
||||||
if(SCENE.message !== undefined) {
|
|
||||||
let bytes = new Uint8Array(event.data);
|
let bytes = new Uint8Array(event.data);
|
||||||
let code = 0;
|
let code = 0;
|
||||||
let index = 2;
|
let index = 2;
|
||||||
@ -81,16 +86,16 @@ function MESSAGE(event) {
|
|||||||
if(bytes.length - index == 26) {
|
if(bytes.length - index == 26) {
|
||||||
data = {
|
data = {
|
||||||
status:(bytes[2] << 8) + bytes[3],
|
status:(bytes[2] << 8) + bytes[3],
|
||||||
token:new Uint8Array(),
|
token:new Uint8Array(8),
|
||||||
secret:new Uint8Array(),
|
secret:new Uint8Array(16),
|
||||||
};
|
};
|
||||||
index += 2;
|
index += 2;
|
||||||
|
|
||||||
for(let i = 0; i < 8; ++i) {
|
for(let i = 0; i < 8; ++i) {
|
||||||
data.token += bytes[index++];
|
data.token[i] = bytes[index++];
|
||||||
}
|
}
|
||||||
for(let i = 0; i < 16; ++i) {
|
for(let i = 0; i < 16; ++i) {
|
||||||
data.secret += bytes[index++];
|
data.secret[i] = bytes[index++];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error("Authenticate packet bad length:" + bytes.length);
|
console.error("Authenticate packet bad length:" + bytes.length);
|
||||||
@ -104,10 +109,10 @@ function MESSAGE(event) {
|
|||||||
result = UNPACK.u16(bytes, index);
|
result = UNPACK.u16(bytes, index);
|
||||||
index = result.index;
|
index = result.index;
|
||||||
if(result.data != Status.Ok) {
|
if(result.data != Status.Ok) {
|
||||||
CONTEXT.Auth = null;
|
sessionStorage.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
LOAD(CONTEXT.Scene);
|
LOAD_URL();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case OpCode.Deauthenticate: {
|
case OpCode.Deauthenticate: {
|
||||||
@ -324,6 +329,7 @@ function MESSAGE(event) {
|
|||||||
|
|
||||||
data = {
|
data = {
|
||||||
status:0,
|
status:0,
|
||||||
|
token:new Uint8Array(8),
|
||||||
dawn:"",
|
dawn:"",
|
||||||
dusk:"",
|
dusk:"",
|
||||||
history:[ ],
|
history:[ ],
|
||||||
@ -334,6 +340,9 @@ function MESSAGE(event) {
|
|||||||
index = result.index;
|
index = result.index;
|
||||||
data.status = result.data;
|
data.status = result.data;
|
||||||
|
|
||||||
|
// Token
|
||||||
|
for(let i = 0; i < 8; ++i) { data.token[i] = bytes[index++]; }
|
||||||
|
|
||||||
// Handles
|
// Handles
|
||||||
result = UNPACK.string(bytes, index);
|
result = UNPACK.string(bytes, index);
|
||||||
index = result.index;
|
index = result.index;
|
||||||
@ -364,10 +373,8 @@ function MESSAGE(event) {
|
|||||||
console.log("RECV Undefined " + code);
|
console.log("RECV Undefined " + code);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(SCENE.message !== undefined) { SCENE.message(code, data) };
|
if(SCENE.message !== undefined) { SCENE.message(code, data) };
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function MESSAGE_COMPOSE(data) {
|
function MESSAGE_COMPOSE(data) {
|
||||||
if(SOCKET !== null) {
|
if(SOCKET !== null) {
|
||||||
|
37
www/js/ui.js
37
www/js/ui.js
@ -90,32 +90,21 @@ const UI = {
|
|||||||
return table;
|
return table;
|
||||||
},
|
},
|
||||||
|
|
||||||
mainnav(left_children, right_children) {
|
mainnav(left_children, right_children, features={}) {
|
||||||
let header = document.createElement("nav");
|
|
||||||
|
|
||||||
let left = document.createElement("section");
|
|
||||||
for(child of left_children) { left.appendChild(child); }
|
|
||||||
|
|
||||||
let right = document.createElement("section");
|
|
||||||
for(child of right_children) { right.appendChild(child); }
|
|
||||||
|
|
||||||
header.appendChild(left);
|
|
||||||
header.appendChild(right);
|
|
||||||
|
|
||||||
MAIN.appendChild(header);
|
|
||||||
},
|
|
||||||
|
|
||||||
mainnav_std(left_children, right_children) {
|
|
||||||
let header = document.createElement("nav");
|
let header = document.createElement("nav");
|
||||||
let left = document.createElement("section");
|
let left = document.createElement("section");
|
||||||
|
|
||||||
if(CONTEXT.Auth === null) {
|
if(sessionStorage.getItem("auth") === null) {
|
||||||
left.appendChild(UI.button("Register", () => { LOAD_STACK(SCENES.Register); }));
|
if(features.auth === true) {
|
||||||
left.appendChild(UI.button("Log In", () => { LOAD_STACK(SCENES.Authenticate); }));
|
left.appendChild(UI.button("Register", () => { LOAD(SCENES.Register); }));
|
||||||
|
left.appendChild(UI.button("Log In", () => { LOAD(SCENES.Authenticate); }));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if(features.session === true) {
|
||||||
left.appendChild(UI.button("Contest", () => { MESSAGE_SESSION_START(); }));
|
left.appendChild(UI.button("Contest", () => { MESSAGE_SESSION_START(); }));
|
||||||
left.appendChild(UI.button("Challenge", () => { }));
|
left.appendChild(UI.button("Challenge", () => { }));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for(child of left_children) { left.appendChild(child); }
|
for(child of left_children) { left.appendChild(child); }
|
||||||
|
|
||||||
@ -144,7 +133,7 @@ const UI = {
|
|||||||
let bottom = [ ];
|
let bottom = [ ];
|
||||||
|
|
||||||
top.push(UI.button("Browse", () => { LOAD(SCENES.Browse); }, page == "browse"));
|
top.push(UI.button("Browse", () => { LOAD(SCENES.Browse); }, page == "browse"));
|
||||||
if(CONTEXT.Auth !== null) {
|
if(sessionStorage.getItem("auth") !== null) {
|
||||||
top.push(UI.button("Continue", () => { LOAD(SCENES.Continue); }, page == "continue"));
|
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"));
|
||||||
}
|
}
|
||||||
@ -154,13 +143,13 @@ const UI = {
|
|||||||
top.push(UI.button("Guide", () => { LOAD(SCENES.Guide); }, page == "guide"));
|
top.push(UI.button("Guide", () => { LOAD(SCENES.Guide); }, page == "guide"));
|
||||||
top.push(UI.button("About", () => { LOAD(SCENES.About); }, page == "about"));
|
top.push(UI.button("About", () => { LOAD(SCENES.About); }, page == "about"));
|
||||||
|
|
||||||
if(CONTEXT.Auth !== null) {
|
if(sessionStorage.getItem("auth") !== null) {
|
||||||
bottom.push(UI.button("Logout", () => {
|
bottom.push(UI.button("Logout", () => {
|
||||||
MESSAGE_COMPOSE([
|
MESSAGE_COMPOSE([
|
||||||
PACK.u16(OpCode.Deauthenticate),
|
PACK.u16(OpCode.Deauthenticate),
|
||||||
]);
|
]);
|
||||||
CONTEXT.Auth = null;
|
sessionStorage.clear();
|
||||||
LOAD(SCENE);
|
LOAD_URL();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +175,7 @@ const UI = {
|
|||||||
if(records[r].player) {
|
if(records[r].player) {
|
||||||
buttons.push(UI.button("Resume", join_callback));
|
buttons.push(UI.button("Resume", join_callback));
|
||||||
} else {
|
} else {
|
||||||
if(CONTEXT.Auth !== null && (records[r].dawn == "" || records[r].dusk == "")) {
|
if(sessionStorage.getItem("auth") !== null && (records[r].dawn == "" || records[r].dusk == "")) {
|
||||||
buttons.push(UI.button("Join", join_callback));
|
buttons.push(UI.button("Join", join_callback));
|
||||||
}
|
}
|
||||||
buttons.push(UI.button("Spectate", spectate_callback));
|
buttons.push(UI.button("Spectate", spectate_callback));
|
||||||
|
@ -13,6 +13,13 @@ const PACK = {
|
|||||||
value & 0xFF
|
value & 0xFF
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
base64(bytes) {
|
||||||
|
let str = "";
|
||||||
|
for(let i = 0; i < bytes.length; ++i) {
|
||||||
|
str += String.fromCharCode(bytes[i]);
|
||||||
|
}
|
||||||
|
return window.btoa(str);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const UNPACK = {
|
const UNPACK = {
|
||||||
@ -107,7 +114,15 @@ const UNPACK = {
|
|||||||
if(take != "") { str += " " + take; }
|
if(take != "") { str += " " + take; }
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
|
},
|
||||||
|
base64(data) {
|
||||||
|
let str = window.atob(data);
|
||||||
|
let bytes = new Uint8Array(str.length);
|
||||||
|
for(let i = 0; i < bytes.length; ++i) {
|
||||||
|
bytes[i] = str.charCodeAt(i);
|
||||||
}
|
}
|
||||||
|
return bytes;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const BITWISE = {
|
const BITWISE = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user