dzura/www/.js
2024-08-08 23:16:22 -07:00

710 lines
20 KiB
JavaScript

let MAIN = null;
let MENU = null;
let SCENE = null;
let CONNECTED = false;
let SOCKET = null;
let CONTEXT = {
Scene: null,
Auth: null,
Data: null,
};
const Status = {
Ok: 0,
Error: 1,
NotImplement: 2,
BadHandle: 1,
BadSecret: 2,
BadCode: 3,
};
const OpCode = {
Register :0x0010,
Authenticate :0x0011,
Resume :0x0012,
Deauthenticate :0x0013,
};
class Message {
constructor(code, data) {
this.code = code;
this.data = data;
}
}
const PACK = {
u8:(value) => {
return new Uint8Array([ value & 0xFF ]);
},
u16:(value) => {
return new Uint8Array([ (value >> 8) & 0xFF, value & 0xFF ]);
},
u32:(value) => {
return new Uint8Array([
(value >> 24) & 0xFF,
(value >> 16) & 0xFF,
(value >> 8) & 0xFF,
value & 0xFF
]);
},
};
const UI = {
text:(value) => {
return document.createTextNode(value);
},
button:(text, callback) => {
let button = document.createElement("button");
button.innerText = text;
if(callback !== null) { button.addEventListener("click", callback); }
return button;
},
textbox:(id, placeholder) => {
let input = document.createElement("input");
input.setAttribute("type", "text");
if(id !== null) { input.setAttribute("id", id); }
input.setAttribute("placeholder", placeholder);
return input;
},
password:(id) => {
let input = document.createElement("input");
input.setAttribute("type", "password");
if(id !== null) { input.setAttribute("id", id); }
return input;
},
label:(name, id) => {
let label = document.createElement("label");
label.setAttribute("for", id);
label.innerText = name;
return label;
},
div:(children) => {
let div = document.createElement("div");
for(child of children) { div.appendChild(child); }
return div;
},
table:(header, rows) => {
let table = document.createElement("table");
let tbody = document.createElement("tbody");
if(header !== null) {
let row = document.createElement("tr");
for(head of header) {
let cell = document.createElement("th");
cell.innerText = head;
row.appendChild(cell);
}
tbody.appendChild(row);
}
for(row of rows) {
let tr = document.createElement("tr");
for(node of row) {
let cell = document.createElement("td");
if(Array.isArray(node)) { for(item of node) { cell.appendChild(item); } }
else { cell.appendChild(node); }
tr.appendChild(cell);
}
tbody.appendChild(tr);
}
table.appendChild(tbody);
return table;
},
mainnav:(left_children, right_children) => {
let header = document.createElement("nav");
let left = document.createElement("section");
if(CONTEXT.Auth === null) {
left.appendChild(UI.button("Register", () => { LOAD(SCENES.Register) }));
left.appendChild(UI.button("Log In", () => { LOAD(SCENES.Authenticate) }));
}
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);
},
mainmenu:() => {
if(SOCKET !== null) {
MENU.appendChild(UI.button("Online", () => { LOAD(SCENES.Online); }));
if(CONTEXT.Auth !== null) {
MENU.appendChild(UI.button("Continue", () => { LOAD(SCENES.Continue); }));
MENU.appendChild(UI.button("Join", () => { LOAD(SCENES.Join); }));
}
MENU.appendChild(UI.button("Live", () => { LOAD(SCENES.Live); }));
MENU.appendChild(UI.button("History", () => { LOAD(SCENES.History); }));
MENU.appendChild(UI.button("Guide", () => { LOAD(SCENES.Guide); }));
MENU.appendChild(UI.button("About", () => { LOAD(SCENES.About); }));
if(CONTEXT.Auth !== null) {
MENU.appendChild(UI.button("Logout", () => {
MESSAGE_COMPOSE([
PACK.u16(OpCode.Deauthenticate),
]);
CONTEXT.Auth = null;
LOAD(SCENE);
}));
}
}
},
};
const SCENES = {
Init:{
load:() => {
LOAD_OFFLINE();
CONTEXT.Scene = SCENES.Online;
RECONNECT();
return true;
},
},
Offline:{
load:() => {
MENU.appendChild(UI.button("Reconnect", () => { RECONNECT(); }))
return true;
},
},
Register:{
load:() => {
if(CONTEXT.Auth !== null) return false;
UI.mainmenu();
UI.mainnav([], []);
let container = document.createElement("section");
let form = document.createElement("div");
form.appendChild(UI.table(null, [
[ UI.label("Handle", "handle"), UI.textbox("handle", "") ],
[ UI.label("Secret", "secret"), UI.password("secret") ],
[ UI.label("Code", "code"), UI.password("code") ],
]));
let button = UI.button("Register", (event) => {
let handle = document.getElementById("handle");
let secret = document.getElementById("secret");
let code = document.getElementById("code");
handle.removeAttribute("class");
secret.removeAttribute("class");
code.removeAttribute("class");
event.target.removeAttribute("class");
if(handle.value.length > 0 && secret.value.length > 0 && code.value.length > 0) {
event.target.setAttribute("disabled", "");
let enc = new TextEncoder();
let enc_handle = enc.encode(handle.value);
let enc_secret = enc.encode(secret.value);
let enc_code = enc.encode(code.value);
MESSAGE_COMPOSE([
PACK.u16(OpCode.Register),
PACK.u16(enc_handle.length),
enc_handle,
PACK.u16(enc_secret.length),
enc_secret,
PACK.u16(enc_code.length),
enc_code,
]);
} else {
if(handle.value.length == 0) { handle.setAttribute("class", "error"); }
if(secret.value.length == 0) { secret.setAttribute("class", "error"); }
if(code.value.length == 0) { code.setAttribute("class", "error"); }
}
});
button.setAttribute("id", "submit");
form.appendChild(button);
container.appendChild(form);
MAIN.appendChild(container);
MAIN.setAttribute("class", "form");
return true;
},
message:(code, data) => {
if(code == OpCode.Register && data !== null) {
let submit = document.getElementById("submit");
switch(data.status) {
case Status.Ok: {
CONTEXT.Auth = data;
LOAD(SCENES.Online);
} break;
default: {
submit.removeAttribute("disabled");
switch(data.status) {
case Status.BadHandle: {
document.getElementById("handle").setAttribute("class", "error");
submit.setAttribute("class", "error");
} break;
case Status.BadSecret: {
document.getElementById("secret").setAttribute("class", "error");
submit.setAttribute("class", "error");
} break;
case Status.BadCode: {
document.getElementById("code").setAttribute("class", "error");
submit.setAttribute("class", "error");
} break;
}
}
}
}
},
},
Authenticate:{
load:() => {
if(CONTEXT.Auth !== null) return false;
UI.mainmenu();
UI.mainnav([], []);
let container = document.createElement("section");
let form = document.createElement("div");
form.appendChild(UI.table(null, [
[ UI.label("Handle", "handle"), UI.textbox("handle", "") ],
[ UI.label("Secret", "secret"), UI.password("secret") ],
]));
let button = UI.button("Register", (event) => {
let handle = document.getElementById("handle");
let secret = document.getElementById("secret");
handle.removeAttribute("class");
secret.removeAttribute("class");
event.target.removeAttribute("class");
if(handle.value.length > 0 && secret.value.length > 0) {
event.target.setAttribute("disabled", "");
let enc = new TextEncoder();
let enc_handle = enc.encode(handle.value);
let enc_secret = enc.encode(secret.value);
MESSAGE_COMPOSE([
PACK.u16(OpCode.Authenticate),
PACK.u16(enc_handle.length),
enc_handle,
PACK.u16(enc_secret.length),
enc_secret,
]);
} else {
if(handle.value.length == 0) { handle.setAttribute("class", "error"); }
if(secret.value.length == 0) { secret.setAttribute("class", "error"); }
}
});
button.setAttribute("id", "submit");
form.appendChild(button);
container.appendChild(form);
MAIN.appendChild(container);
MAIN.setAttribute("class", "form");
return true;
},
message:(code, data) => {
if(code == OpCode.Authenticate && data !== null) {
let submit = document.getElementById("submit");
switch(data.status) {
case Status.Ok: {
CONTEXT.Auth = data;
LOAD(SCENES.Online);
} break;
case Status.Error: {
submit.removeAttribute("disabled");
document.getElementById("handle").setAttribute("class", "error");
document.getElementById("secret").setAttribute("class", "error");
submit.setAttribute("class", "error");
}
}
}
},
},
Online:{
load:() => {
UI.mainmenu();
let left_buttons = [ ];
if(CONTEXT.Auth !== null) {
left_buttons.push(UI.button("Start", null));
}
UI.mainnav(
left_buttons,
[
UI.div([UI.text("0 - 0 of 0")]),
UI.button("◀", null),
UI.button("▶", null),
]
);
let table = document.createElement("table");
table.setAttribute("id", "content");
MAIN.appendChild(table);
MAIN.setAttribute("class", "list");
SCENE.refresh();
return true;
},
refresh:() => {
let request = new Uint8Array();
//SERVER.send()
SCENE.message(0, 0, null);
},
message:(code, data) => {
let table = document.getElementById("content");
MAIN.removeChild(table);
let rows = [
[ UI.text("Player1"), UI.text("Player2"), UI.text("0"), UI.text("Ha1-D ◈ Ba1-M"), UI.text("0"), [ UI.button("Join", null), UI.button("Spectate", null) ] ],
];
MAIN.appendChild(UI.table(
[ "Dawn", "Dusk", "Turn", "Move", "Spectators", "" ],
rows,
));
}
},
Continue:{
load:() => {
if(CONTEXT.Auth === null) return false;
UI.mainmenu();
let left_buttons = [ ];
if(CONTEXT.Auth !== null) {
left_buttons.push(UI.button("Start", null));
}
UI.mainnav(
left_buttons,
[
UI.div([UI.text("0 - 0 of 0")]),
UI.button("◀", null),
UI.button("▶", null),
]
);
let table = document.createElement("table");
table.setAttribute("id", "content");
MAIN.appendChild(table);
MAIN.setAttribute("class", "list");
SCENE.refresh();
return true;
},
refresh:() => {
},
},
Join:{
load:() => {
if(CONTEXT.Auth === null) return false;
UI.mainmenu();
let left_buttons = [ ];
if(CONTEXT.Auth !== null) {
left_buttons.push(UI.button("Start", null));
}
UI.mainnav(
left_buttons,
[
UI.div([UI.text("0 - 0 of 0")]),
UI.button("◀", null),
UI.button("▶", null),
]
);
let table = document.createElement("table");
table.setAttribute("id", "content");
MAIN.appendChild(table);
MAIN.setAttribute("class", "list");
SCENE.refresh();
return true;
},
refresh:() => {
},
},
Live:{
load:() => {
UI.mainmenu();
let left_buttons = [ ];
if(CONTEXT.Auth !== null) {
left_buttons.push(UI.button("Start", null));
}
UI.mainnav(
left_buttons,
[
UI.div([UI.text("0 - 0 of 0")]),
UI.button("◀", null),
UI.button("▶", null),
]
);
let table = document.createElement("table");
table.setAttribute("id", "content");
MAIN.appendChild(table);
MAIN.setAttribute("class", "list");
SCENE.refresh();
return true;
},
refresh:() => {
},
},
History:{
load:() => {
UI.mainmenu();
UI.mainnav(
[ ],
[
UI.div([UI.text("0 - 0 of 0")]),
UI.button("◀", null),
UI.button("▶", null),
]
);
let table = document.createElement("table");
table.setAttribute("id", "content");
MAIN.appendChild(table);
MAIN.setAttribute("class", "list");
SCENE.refresh();
return true;
},
refresh:() => {
},
},
Guide:{
load:() => {
UI.mainmenu();
UI.mainnav([], []);
return true;
},
refresh:() => {
},
},
About:{
load:() => {
UI.mainmenu();
UI.mainnav([], []);
return true;
},
refresh:() => {
},
},
Game:{
load:() => {
MENU.appendChild(UI.button("Back", () => { LOAD(SCENES.Online) }));
MENU.appendChild(UI.button("Retire", () => { }));
return true;
},
unload:() => {
},
refresh:() => {
},
},
};
function RECONNECT() {
if(SOCKET === null) {
console.log("Websocket connecting..");
SOCKET = new WebSocket("wss://omen.kirisame.com:38612");
SOCKET.binaryType = "arraybuffer";
SOCKET.addEventListener("error", (event) => {
SOCKET = null;
LOAD(SCENES.Offline)
});
SOCKET.addEventListener("open", (event) => {
if(SOCKET.readyState === WebSocket.OPEN) {
console.log("Websocket connected.");
SOCKET.addEventListener("message", MESSAGE);
SOCKET.addEventListener("close", (event) => {
console.log("Websocket closed.");
SOCKET = null;
RECONNECT();
});
RESUME();
}
});
}
}
function RESUME() {
LOAD(CONTEXT.Scene);
}
function REBUILD() {
MENU = document.createElement("nav");
let title = document.createElement("header");
title.innerText = "Omen";
MENU.appendChild(title);
MAIN = document.createElement("main");
document.body.appendChild(MENU);
document.body.appendChild(MAIN);
}
function MESSAGE(event) {
console.log("Message received.");
if(SCENE.message !== undefined) {
let bytes = new Uint8Array(event.data);
let code = 0;
let index = 2;
let data = null;
if(bytes.length >= 2) {
code = (bytes[0] << 8) + bytes[1];
}
switch(code) {
case OpCode.Register: {
console.log("Register response.");
if(bytes.length == 28) {
console.log("Good size");
data = {
status:(bytes[2] << 8) + bytes[3],
token:new Uint8Array(),
secret:new Uint8Array(),
};
index += 2;
for(let i = 0; i < 8; ++i) {
data.token += bytes[index++];
}
for(let i = 0; i < 16; ++i) {
data.secret += bytes[index++];
}
} else {
console.log("Register bad length:" + bytes.length);
return;
}
} break;
case OpCode.Authenticate: {
console.log("Authenticate response.");
if(bytes.length == 28) {
console.log("Good size");
data = {
status:(bytes[2] << 8) + bytes[3],
token:new Uint8Array(),
secret:new Uint8Array(),
};
index += 2;
for(let i = 0; i < 8; ++i) {
data.token += bytes[index++];
}
for(let i = 0; i < 16; ++i) {
data.secret += bytes[index++];
}
} else {
console.log("Authenticate bad length:" + bytes.length);
return;
}
} break;
case OpCode.Resume: {
} break;
case OpCode.Deauthenticate: {
} break;
default:
return;
}
SCENE.message(code, data);
}
}
function MESSAGE_COMPOSE(data) {
if(SOCKET !== null) {
let length = 0;
for(let i = 0; i < data.length; ++i) {
length += data[i].length;
}
let raw = new Uint8Array(length);
length = 0;
for(let i = 0; i < data.length; ++i) {
raw.set(data[i], length);
length += data[i].length;
}
SOCKET.send(raw);
}
}
function LOAD(scene) {
if(SCENE.unload !== undefined) { SCENE.unload(); }
while(document.body.lastChild !== null) { document.body.removeChild(document.body.lastChild); }
REBUILD();
SCENE = scene;
CONTEXT.Scene = SCENE;
if(!SCENE.load()) { LOAD(SCENES.Online); }
}
function LOAD_OFFLINE() {
if(SCENE.unload !== undefined) { SCENE.unload(); }
while(document.body.lastChild !== null) { document.body.removeChild(document.body.lastChild); }
REBUILD();
SCENE = SCENES.Offline;
if(!SCENE.load()) { LOAD(SCENES.Online); }
}
document.addEventListener("DOMContentLoaded", () => {
SCENE = SCENES.Offline;
LOAD(SCENES.Init);
});