initial refactoring to use a pure html/js login screen
This commit is contained in:
parent
457d6cee96
commit
2ba48354ac
@ -6,8 +6,8 @@ pub type NewUserState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub type NewUserClientMessages {
|
pub type NewUserClientMessages {
|
||||||
Register(game_code: String, username: String)
|
JoinAsPlayer(token: String, user_id: Int, game_id: String)
|
||||||
CreateRoom(game_code: String, username: String)
|
JoinAsHost(token: String, user_id: Int, game_id: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_user_client_messages_decoder() -> decode.Decoder(
|
pub fn new_user_client_messages_decoder() -> decode.Decoder(
|
||||||
@ -15,40 +15,32 @@ pub fn new_user_client_messages_decoder() -> decode.Decoder(
|
|||||||
) {
|
) {
|
||||||
use variant <- decode.field("type", decode.string)
|
use variant <- decode.field("type", decode.string)
|
||||||
case variant {
|
case variant {
|
||||||
"Register" -> {
|
"JoinAsPlayer" -> {
|
||||||
use game_code <- decode.field("game_code", decode.string)
|
use token <- decode.field("token", decode.string)
|
||||||
use username <- decode.field("username", decode.string)
|
use user_id <- decode.field("user_id", decode.int)
|
||||||
decode.success(Register(game_code:, username:))
|
use game_id <- decode.field("game_id", decode.string)
|
||||||
|
decode.success(JoinAsPlayer(token:, user_id:, game_id:))
|
||||||
}
|
}
|
||||||
"CreateRoom" -> {
|
"JoinAsHost" -> {
|
||||||
use game_code <- decode.field("game_code", decode.string)
|
use token <- decode.field("token", decode.string)
|
||||||
use username <- decode.field("username", decode.string)
|
use user_id <- decode.field("user_id", decode.int)
|
||||||
decode.success(CreateRoom(game_code:, username:))
|
use game_id <- decode.field("game_id", decode.string)
|
||||||
|
decode.success(JoinAsHost(token:, user_id:, game_id:))
|
||||||
}
|
}
|
||||||
_ -> decode.failure(Register("", ""), "NewUserClientMessages")
|
_ -> decode.failure(JoinAsPlayer("", 0, ""), "NewUserClientMessages")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type NewUserServerMessages {
|
pub type NewUserServerMessages {
|
||||||
RegisterResponse(username: String, token: String)
|
RegisterResponse
|
||||||
HostRegisterResponse(username: String, token: String)
|
HostRegisterResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode_new_user_server_messages(
|
pub fn encode_new_user_server_messages(
|
||||||
new_user_server_messages: NewUserServerMessages,
|
new_user_server_messages: NewUserServerMessages,
|
||||||
) -> json.Json {
|
) -> json.Json {
|
||||||
case new_user_server_messages {
|
case new_user_server_messages {
|
||||||
RegisterResponse(..) ->
|
RegisterResponse -> json.string("register_response")
|
||||||
json.object([
|
HostRegisterResponse -> json.string("host_register_response")
|
||||||
#("type", json.string("RegisterResponse")),
|
|
||||||
#("username", json.string(new_user_server_messages.username)),
|
|
||||||
#("token", json.string(new_user_server_messages.token)),
|
|
||||||
])
|
|
||||||
HostRegisterResponse(..) ->
|
|
||||||
json.object([
|
|
||||||
#("type", json.string("HostRegisterResponse")),
|
|
||||||
#("username", json.string(new_user_server_messages.username)),
|
|
||||||
#("token", json.string(new_user_server_messages.token)),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,30 @@
|
|||||||
import app/clients/new_user_client
|
import app/clients/new_user_client
|
||||||
import app/web
|
import app/web
|
||||||
|
import gleam/bit_array
|
||||||
import gleam/bytes_tree
|
import gleam/bytes_tree
|
||||||
|
import gleam/dict
|
||||||
import gleam/erlang/process
|
import gleam/erlang/process
|
||||||
|
import gleam/http/cookie
|
||||||
import gleam/http/request.{type Request}
|
import gleam/http/request.{type Request}
|
||||||
import gleam/http/response.{type Response}
|
import gleam/http/response.{type Response}
|
||||||
import gleam/int
|
import gleam/int
|
||||||
import gleam/io
|
import gleam/io
|
||||||
|
import gleam/json
|
||||||
import gleam/option.{None, Some}
|
import gleam/option.{None, Some}
|
||||||
import gleam/otp/actor
|
import gleam/otp/actor
|
||||||
import gleam/result
|
import gleam/result
|
||||||
import gleam/string
|
import gleam/string
|
||||||
|
import login
|
||||||
import mist.{type Connection, type ResponseData}
|
import mist.{type Connection, type ResponseData}
|
||||||
|
import player
|
||||||
|
import session
|
||||||
import socket_manager
|
import socket_manager
|
||||||
|
import storail
|
||||||
|
|
||||||
|
type JoinAs {
|
||||||
|
JoinAsHost
|
||||||
|
JoinAsPlayer
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_request(
|
pub fn handle_request(
|
||||||
req: Request(Connection),
|
req: Request(Connection),
|
||||||
@ -25,10 +38,75 @@ pub fn handle_request(
|
|||||||
["static", "frontend_bg.wasm"] ->
|
["static", "frontend_bg.wasm"] ->
|
||||||
serve_wasm(ctx, "frontend_bg.wasm", "application/wasm")
|
serve_wasm(ctx, "frontend_bg.wasm", "application/wasm")
|
||||||
["ws"] -> serve_websocket(ctx, req)
|
["ws"] -> serve_websocket(ctx, req)
|
||||||
|
["login", "host"] -> server_login(ctx, req, JoinAsHost)
|
||||||
|
["login", "join"] -> server_login(ctx, req, JoinAsPlayer)
|
||||||
_ -> response.set_body(response.new(404), mist.Bytes(bytes_tree.new()))
|
_ -> response.set_body(response.new(404), mist.Bytes(bytes_tree.new()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn server_login(
|
||||||
|
ctx: web.Context,
|
||||||
|
req: Request(Connection),
|
||||||
|
join_type: JoinAs,
|
||||||
|
) -> Response(ResponseData) {
|
||||||
|
let assert Ok(req) = mist.read_body(req, 1000)
|
||||||
|
let assert Ok(body) = bit_array.to_string(req.body)
|
||||||
|
|
||||||
|
let assert Ok(login_req) = json.parse(body, login.login_request_decoder())
|
||||||
|
let #(token, user_id) =
|
||||||
|
web.register_user(ctx, login_req.username, login_req.game_code)
|
||||||
|
|
||||||
|
let _ = case join_type {
|
||||||
|
JoinAsHost -> {
|
||||||
|
let key = storail.key(ctx.sessions, login_req.game_code)
|
||||||
|
let session = session.Session(login_req.game_code, user_id, dict.new())
|
||||||
|
let assert Ok(Nil) = storail.write(key, session)
|
||||||
|
}
|
||||||
|
JoinAsPlayer -> {
|
||||||
|
let key = storail.key(ctx.sessions, login_req.game_code)
|
||||||
|
let assert Ok(session) = storail.read(key)
|
||||||
|
|
||||||
|
let player =
|
||||||
|
player.Player(
|
||||||
|
name: login_req.username,
|
||||||
|
id: user_id,
|
||||||
|
score: 0,
|
||||||
|
buzz_in_time: None,
|
||||||
|
)
|
||||||
|
let session =
|
||||||
|
session.Session(
|
||||||
|
..session,
|
||||||
|
players: dict.insert(session.players, player.id, player),
|
||||||
|
)
|
||||||
|
|
||||||
|
let assert Ok(Nil) = storail.write(key, session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let join_type_name = case join_type {
|
||||||
|
JoinAsHost -> "host"
|
||||||
|
JoinAsPlayer -> "player"
|
||||||
|
}
|
||||||
|
|
||||||
|
response.new(200)
|
||||||
|
|> response.set_cookie(
|
||||||
|
"play_of_the_game/token",
|
||||||
|
token,
|
||||||
|
cookie.defaults(req.scheme),
|
||||||
|
)
|
||||||
|
|> response.set_cookie(
|
||||||
|
"play_of_the_game/client_type",
|
||||||
|
join_type_name,
|
||||||
|
cookie.defaults(req.scheme),
|
||||||
|
)
|
||||||
|
|> response.set_cookie(
|
||||||
|
"play_of_the_game/username",
|
||||||
|
login_req.username,
|
||||||
|
cookie.defaults(req.scheme),
|
||||||
|
)
|
||||||
|
|> response.set_body(mist.Bytes(bytes_tree.new()))
|
||||||
|
}
|
||||||
|
|
||||||
fn serve_wasm(
|
fn serve_wasm(
|
||||||
ctx: web.Context,
|
ctx: web.Context,
|
||||||
file: String,
|
file: String,
|
||||||
|
@ -19,6 +19,10 @@ import session
|
|||||||
import socket_manager
|
import socket_manager
|
||||||
import storail
|
import storail
|
||||||
|
|
||||||
|
pub type Error {
|
||||||
|
InvalidToken
|
||||||
|
}
|
||||||
|
|
||||||
pub type Context {
|
pub type Context {
|
||||||
Context(
|
Context(
|
||||||
config: config.Config,
|
config: config.Config,
|
||||||
@ -109,8 +113,8 @@ fn handle_player(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_user(
|
pub fn register_user(
|
||||||
state: SocketState,
|
ctx: Context,
|
||||||
username: String,
|
username: String,
|
||||||
game_code: String,
|
game_code: String,
|
||||||
) -> #(String, Int) {
|
) -> #(String, Int) {
|
||||||
@ -127,7 +131,7 @@ fn register_user(
|
|||||||
|
|
||||||
let assert <<user_id:int-size(64)>> = crypto.strong_random_bytes(8)
|
let assert <<user_id:int-size(64)>> = crypto.strong_random_bytes(8)
|
||||||
|
|
||||||
let key = storail.key(state.ctx.player_sessions, int.to_base16(user_id))
|
let key = storail.key(ctx.player_sessions, int.to_base16(user_id))
|
||||||
|
|
||||||
let assert Ok(Nil) =
|
let assert Ok(Nil) =
|
||||||
storail.write(
|
storail.write(
|
||||||
@ -136,13 +140,40 @@ fn register_user(
|
|||||||
id: user_id,
|
id: user_id,
|
||||||
token_hash:,
|
token_hash:,
|
||||||
username:,
|
username:,
|
||||||
socket_id: state.id,
|
socket_id: None,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
#(token, user_id)
|
#(token, user_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn attach_socket_to_user(
|
||||||
|
state: SocketState,
|
||||||
|
user_id: Int,
|
||||||
|
token: String,
|
||||||
|
) -> Result(Nil, Error) {
|
||||||
|
let key = storail.key(state.ctx.player_sessions, int.to_base16(user_id))
|
||||||
|
let assert Ok(player_state) = storail.read(key)
|
||||||
|
|
||||||
|
let hashed_token = player_session.hash_token(token)
|
||||||
|
|
||||||
|
case hashed_token == player_state.token_hash {
|
||||||
|
False -> Error(InvalidToken)
|
||||||
|
True -> {
|
||||||
|
let assert Ok(Nil) =
|
||||||
|
storail.write(
|
||||||
|
key,
|
||||||
|
player_session.PlayerSession(
|
||||||
|
..player_state,
|
||||||
|
socket_id: Some(state.id),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
Ok(Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_new_user(
|
fn handle_new_user(
|
||||||
socket_state: SocketState,
|
socket_state: SocketState,
|
||||||
_new_user_state: new_user_client.NewUserState,
|
_new_user_state: new_user_client.NewUserState,
|
||||||
@ -152,49 +183,25 @@ fn handle_new_user(
|
|||||||
json.parse(msg, new_user_client.new_user_client_messages_decoder())
|
json.parse(msg, new_user_client.new_user_client_messages_decoder())
|
||||||
|
|
||||||
let #(socket_state, new_user_msg) = case msg {
|
let #(socket_state, new_user_msg) = case msg {
|
||||||
new_user_client.CreateRoom(game_code, username) -> {
|
new_user_client.JoinAsHost(token, user_id, game_id) -> {
|
||||||
let #(token, user_id) = register_user(socket_state, username, game_code)
|
let assert Ok(_) = attach_socket_to_user(socket_state, user_id, token)
|
||||||
|
|
||||||
let key = storail.key(socket_state.ctx.sessions, game_code)
|
|
||||||
let session = session.Session(game_code, user_id, dict.new())
|
|
||||||
let assert Ok(Nil) = storail.write(key, session)
|
|
||||||
|
|
||||||
#(
|
#(
|
||||||
SocketState(
|
SocketState(
|
||||||
..socket_state,
|
..socket_state,
|
||||||
state: Host(host_client.HostState(user_id, game_code)),
|
state: Player(player_client.PlayerState(user_id:, game_id:)),
|
||||||
),
|
),
|
||||||
new_user_client.HostRegisterResponse(username, token),
|
new_user_client.HostRegisterResponse,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
new_user_client.Register(game_code, username) -> {
|
new_user_client.JoinAsPlayer(token, user_id, game_id) -> {
|
||||||
let #(token, user_id) = register_user(socket_state, username, game_code)
|
let assert Ok(_) = attach_socket_to_user(socket_state, user_id, token)
|
||||||
|
|
||||||
let key = storail.key(socket_state.ctx.sessions, game_code)
|
|
||||||
let assert Ok(session) = storail.read(key)
|
|
||||||
|
|
||||||
let player =
|
|
||||||
player.Player(name: username, id: user_id, score: 0, buzz_in_time: None)
|
|
||||||
let session =
|
|
||||||
session.Session(
|
|
||||||
..session,
|
|
||||||
players: dict.insert(session.players, player.id, player),
|
|
||||||
)
|
|
||||||
|
|
||||||
send_message_to_host(
|
|
||||||
socket_state,
|
|
||||||
session.host_user_id,
|
|
||||||
host_client.UpdatePlayerStates(session.players),
|
|
||||||
)
|
|
||||||
|
|
||||||
let assert Ok(Nil) = storail.write(key, session)
|
|
||||||
|
|
||||||
#(
|
#(
|
||||||
SocketState(
|
SocketState(
|
||||||
..socket_state,
|
..socket_state,
|
||||||
state: Player(player_client.PlayerState(user_id, game_code)),
|
state: Host(host_client.HostState(user_id:, game_id:)),
|
||||||
),
|
),
|
||||||
new_user_client.RegisterResponse(username, token),
|
new_user_client.HostRegisterResponse,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,8 +225,9 @@ fn get_player_session_subject(
|
|||||||
ctx: Context,
|
ctx: Context,
|
||||||
player: player_session.PlayerSession,
|
player: player_session.PlayerSession,
|
||||||
) -> process.Subject(String) {
|
) -> process.Subject(String) {
|
||||||
|
let assert Some(id) = player.socket_id
|
||||||
let assert Ok(subject) =
|
let assert Ok(subject) =
|
||||||
socket_manager.get_socket_subj_by_id(ctx.socket_manager, player.socket_id)
|
socket_manager.get_socket_subj_by_id(ctx.socket_manager, id)
|
||||||
subject
|
subject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
72
frontend/index/index.html
Normal file
72
frontend/index/index.html
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Play of the Game</title>
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
canvas {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; padding: 0; height: 100vh; width: 100vw">
|
||||||
|
<canvas id="glcanvas" tabindex="1" hidden></canvas>
|
||||||
|
<script src="https://not-fl3.github.io/miniquad-samples/mq_js_bundle.js"></script>
|
||||||
|
<script type="module">
|
||||||
|
import init, { set_wasm } from "./static/frontend.js";
|
||||||
|
async function impl_run() {
|
||||||
|
let wbg = await init();
|
||||||
|
miniquad_add_plugin({
|
||||||
|
register_plugin: (a) => (a.wbg = wbg),
|
||||||
|
on_init: () => set_wasm(wasm_exports),
|
||||||
|
version: "0.0.1",
|
||||||
|
name: "wbg",
|
||||||
|
});
|
||||||
|
load("./static/frontend_bg.wasm");
|
||||||
|
}
|
||||||
|
window.run = function (join_type) {
|
||||||
|
console.log("Joining as: ", join_type);
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open("POST", "./login/" + join_type);
|
||||||
|
xhr.onload = function (event) {
|
||||||
|
console.log("Starting game...", event.target);
|
||||||
|
document.getElementById("run-container").remove();
|
||||||
|
document
|
||||||
|
.getElementById("glcanvas")
|
||||||
|
.removeAttribute("hidden");
|
||||||
|
document.getElementById("glcanvas").focus();
|
||||||
|
impl_run();
|
||||||
|
};
|
||||||
|
// or onerror, onabort
|
||||||
|
var formData = new FormData(document.getElementById("login"));
|
||||||
|
xhr.send(JSON.stringify(Object.fromEntries(formData));
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<div
|
||||||
|
id="run-container"
|
||||||
|
style="
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<form id="login">
|
||||||
|
<label for="username">Username:</label><br />
|
||||||
|
<input type="text" id="username" name="username" /><br />
|
||||||
|
<label for="gamecode">Gamecode:</label><br />
|
||||||
|
<input type="text" id="gamecode" name="gamecode" /><br />
|
||||||
|
</form>
|
||||||
|
<button onclick="run('join')">Join Game</button>
|
||||||
|
<button onclick="run('host')">Host Game</button>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
36
shared/src/login.gleam
Normal file
36
shared/src/login.gleam
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import gleam/dynamic/decode
|
||||||
|
import gleam/json
|
||||||
|
|
||||||
|
pub type LoginRequest {
|
||||||
|
LoginRequest(username: String, game_code: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode_login_request(login_request: LoginRequest) -> json.Json {
|
||||||
|
json.object([
|
||||||
|
#("username", json.string(login_request.username)),
|
||||||
|
#("game_code", json.string(login_request.game_code)),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn login_request_decoder() -> decode.Decoder(LoginRequest) {
|
||||||
|
use username <- decode.field("username", decode.string)
|
||||||
|
use game_code <- decode.field("game_code", decode.string)
|
||||||
|
decode.success(LoginRequest(username:, game_code:))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type LoginResponse {
|
||||||
|
LoginResponse(username: String, token: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode_login_response(login_response: LoginResponse) -> json.Json {
|
||||||
|
json.object([
|
||||||
|
#("username", json.string(login_response.username)),
|
||||||
|
#("token", json.string(login_response.token)),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn login_response_decoder() -> decode.Decoder(LoginResponse) {
|
||||||
|
use username <- decode.field("username", decode.string)
|
||||||
|
use token <- decode.field("token", decode.string)
|
||||||
|
decode.success(LoginResponse(username:, token:))
|
||||||
|
}
|
@ -2,9 +2,15 @@ import gleam/bit_array
|
|||||||
import gleam/crypto
|
import gleam/crypto
|
||||||
import gleam/dynamic/decode
|
import gleam/dynamic/decode
|
||||||
import gleam/json
|
import gleam/json
|
||||||
|
import gleam/option.{type Option}
|
||||||
|
|
||||||
pub type PlayerSession {
|
pub type PlayerSession {
|
||||||
PlayerSession(id: Int, token_hash: String, username: String, socket_id: Int)
|
PlayerSession(
|
||||||
|
id: Int,
|
||||||
|
token_hash: String,
|
||||||
|
username: String,
|
||||||
|
socket_id: Option(Int),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode_player_session(player_session: PlayerSession) -> json.Json {
|
pub fn encode_player_session(player_session: PlayerSession) -> json.Json {
|
||||||
@ -12,7 +18,10 @@ pub fn encode_player_session(player_session: PlayerSession) -> json.Json {
|
|||||||
#("id", json.int(player_session.id)),
|
#("id", json.int(player_session.id)),
|
||||||
#("token_hash", json.string(player_session.token_hash)),
|
#("token_hash", json.string(player_session.token_hash)),
|
||||||
#("username", json.string(player_session.username)),
|
#("username", json.string(player_session.username)),
|
||||||
#("socket_id", json.int(player_session.socket_id)),
|
#("socket_id", case player_session.socket_id {
|
||||||
|
option.None -> json.null()
|
||||||
|
option.Some(value) -> json.int(value)
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,12 +29,11 @@ pub fn player_session_decoder() -> decode.Decoder(PlayerSession) {
|
|||||||
use id <- decode.field("id", decode.int)
|
use id <- decode.field("id", decode.int)
|
||||||
use token_hash <- decode.field("token_hash", decode.string)
|
use token_hash <- decode.field("token_hash", decode.string)
|
||||||
use username <- decode.field("username", decode.string)
|
use username <- decode.field("username", decode.string)
|
||||||
use socket_id <- decode.field("socket_id", decode.int)
|
use socket_id <- decode.field("socket_id", decode.optional(decode.int))
|
||||||
decode.success(PlayerSession(id:, token_hash:, username:, socket_id:))
|
decode.success(PlayerSession(id:, token_hash:, username:, socket_id:))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hash_token(token: String) -> String {
|
pub fn hash_token(token: String) -> String {
|
||||||
let token =
|
|
||||||
crypto.new_hasher(crypto.Sha512)
|
crypto.new_hasher(crypto.Sha512)
|
||||||
|> crypto.hash_chunk(bit_array.from_string(token))
|
|> crypto.hash_chunk(bit_array.from_string(token))
|
||||||
|> crypto.digest
|
|> crypto.digest
|
||||||
|
Loading…
x
Reference in New Issue
Block a user