handle cross socket message passing
This commit is contained in:
parent
b6a4936247
commit
af0fd811b3
@ -24,7 +24,7 @@ packages = [
|
|||||||
{ name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" },
|
{ name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" },
|
||||||
{ name = "mist", version = "4.0.7", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "gleam_yielder", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "F7D15A1E3232E124C7CE31900253633434E59B34ED0E99F273DEE61CDB573CDD" },
|
{ name = "mist", version = "4.0.7", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "gleam_yielder", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "F7D15A1E3232E124C7CE31900253633434E59B34ED0E99F273DEE61CDB573CDD" },
|
||||||
{ name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" },
|
{ name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" },
|
||||||
{ name = "shared", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], source = "local", path = "../shared" },
|
{ name = "shared", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_json", "gleam_stdlib"], source = "local", path = "../shared" },
|
||||||
{ name = "simplifile", version = "2.2.1", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "C88E0EE2D509F6D86EB55161D631657675AA7684DAB83822F7E59EB93D9A60E3" },
|
{ name = "simplifile", version = "2.2.1", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "C88E0EE2D509F6D86EB55161D631657675AA7684DAB83822F7E59EB93D9A60E3" },
|
||||||
{ name = "snag", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "7E9F06390040EB5FAB392CE642771484136F2EC103A92AE11BA898C8167E6E17" },
|
{ name = "snag", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "7E9F06390040EB5FAB392CE642771484136F2EC103A92AE11BA898C8167E6E17" },
|
||||||
{ name = "storail", version = "3.0.0", build_tools = ["gleam"], requirements = ["directories", "filepath", "gleam_crypto", "gleam_json", "gleam_stdlib", "simplifile"], otp_app = "storail", source = "hex", outer_checksum = "D032EE5C89AA4B6306FF81929BF9B5DD2583E9F743F0047788AE5F1F52AFE3B4" },
|
{ name = "storail", version = "3.0.0", build_tools = ["gleam"], requirements = ["directories", "filepath", "gleam_crypto", "gleam_json", "gleam_stdlib", "simplifile"], otp_app = "storail", source = "hex", outer_checksum = "D032EE5C89AA4B6306FF81929BF9B5DD2583E9F743F0047788AE5F1F52AFE3B4" },
|
||||||
|
@ -3,7 +3,7 @@ import gleam/bit_array
|
|||||||
import gleam/bytes_tree
|
import gleam/bytes_tree
|
||||||
import gleam/crypto
|
import gleam/crypto
|
||||||
import gleam/dynamic/decode
|
import gleam/dynamic/decode
|
||||||
import gleam/erlang/process
|
import gleam/erlang/process.{type Subject}
|
||||||
import gleam/float
|
import gleam/float
|
||||||
import gleam/http/request.{type Request}
|
import gleam/http/request.{type Request}
|
||||||
import gleam/http/response.{type Response}
|
import gleam/http/response.{type Response}
|
||||||
@ -11,7 +11,7 @@ import gleam/int
|
|||||||
import gleam/io
|
import gleam/io
|
||||||
import gleam/json
|
import gleam/json
|
||||||
import gleam/list
|
import gleam/list
|
||||||
import gleam/option.{None, Some}
|
import gleam/option.{type Option, None, Some}
|
||||||
import gleam/otp/actor
|
import gleam/otp/actor
|
||||||
import gleam/result
|
import gleam/result
|
||||||
import gleam/string
|
import gleam/string
|
||||||
@ -19,6 +19,7 @@ import mist.{type Connection, type ResponseData}
|
|||||||
import player
|
import player
|
||||||
import player_session
|
import player_session
|
||||||
import session
|
import session
|
||||||
|
import socket_manager
|
||||||
import storail
|
import storail
|
||||||
|
|
||||||
pub fn handle_request(
|
pub fn handle_request(
|
||||||
@ -55,14 +56,39 @@ fn serve_wasm(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type SocketState {
|
||||||
|
SocketState(
|
||||||
|
ctx: web.Context,
|
||||||
|
id: Int,
|
||||||
|
subject: Subject(web.GameResponse),
|
||||||
|
user_id: Option(Int),
|
||||||
|
game_id: Option(String),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn serve_websocket(
|
fn serve_websocket(
|
||||||
ctx: web.Context,
|
ctx: web.Context,
|
||||||
req: Request(Connection),
|
req: Request(Connection),
|
||||||
) -> Response(ResponseData) {
|
) -> Response(ResponseData) {
|
||||||
mist.websocket(
|
mist.websocket(
|
||||||
request: req,
|
request: req,
|
||||||
on_init: fn(_conn) { #(ctx, Some(ctx.selector)) },
|
on_init: fn(_conn) {
|
||||||
on_close: fn(_state) { io.println("goodbye!") },
|
let self = process.new_subject()
|
||||||
|
|
||||||
|
let selector =
|
||||||
|
process.new_selector()
|
||||||
|
|> process.selecting(self, fn(x) { x })
|
||||||
|
|
||||||
|
let id = socket_manager.add_socket_subj(ctx.socket_manager, self)
|
||||||
|
|
||||||
|
let state =
|
||||||
|
SocketState(ctx:, id:, subject: self, user_id: None, game_id: None)
|
||||||
|
#(state, Some(selector))
|
||||||
|
},
|
||||||
|
on_close: fn(state) {
|
||||||
|
io.println("Shutting down socket" <> int.to_string(state.id))
|
||||||
|
socket_manager.remove_socket_subj(state.ctx.socket_manager, state.subject)
|
||||||
|
},
|
||||||
handler: handle_ws_message,
|
handler: handle_ws_message,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -80,7 +106,11 @@ fn decode_errors_to_string(decode_errs: List(decode.DecodeError)) -> String {
|
|||||||
string.join(list.map(decode_errs, decode_error_to_string), "\n")
|
string.join(list.map(decode_errs, decode_error_to_string), "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_ws_message(state, conn, message) {
|
fn handle_ws_message(
|
||||||
|
state: SocketState,
|
||||||
|
conn,
|
||||||
|
message: mist.WebsocketMessage(web.GameResponse),
|
||||||
|
) {
|
||||||
io.println("Got WS message")
|
io.println("Got WS message")
|
||||||
case message {
|
case message {
|
||||||
mist.Text("ping") -> {
|
mist.Text("ping") -> {
|
||||||
@ -90,7 +120,7 @@ fn handle_ws_message(state, conn, message) {
|
|||||||
}
|
}
|
||||||
mist.Text(msg) -> {
|
mist.Text(msg) -> {
|
||||||
echo msg
|
echo msg
|
||||||
case json.parse(msg, web.client_request_decoder()) {
|
let state = case json.parse(msg, web.client_request_decoder()) {
|
||||||
Error(err) -> {
|
Error(err) -> {
|
||||||
let err_msg = case err {
|
let err_msg = case err {
|
||||||
json.UnableToDecode(decode_err) ->
|
json.UnableToDecode(decode_err) ->
|
||||||
@ -102,16 +132,17 @@ fn handle_ws_message(state, conn, message) {
|
|||||||
"Unexpected Sequence: " <> seq_msg
|
"Unexpected Sequence: " <> seq_msg
|
||||||
}
|
}
|
||||||
io.println("Could not parse client message: " <> err_msg)
|
io.println("Could not parse client message: " <> err_msg)
|
||||||
|
state
|
||||||
}
|
}
|
||||||
Ok(req) -> {
|
Ok(req) -> {
|
||||||
let resp = handle_client_msg(state, req)
|
let #(state, resp) = handle_client_msg(state, req)
|
||||||
echo resp
|
echo resp
|
||||||
let resp_str =
|
let resp_str =
|
||||||
web.encode_game_response(resp)
|
web.encode_game_response(resp)
|
||||||
|> json.to_string
|
|> json.to_string
|
||||||
|
|
||||||
let assert Ok(_) = mist.send_text_frame(conn, resp_str)
|
let assert Ok(_) = mist.send_text_frame(conn, resp_str)
|
||||||
Nil
|
state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +151,12 @@ fn handle_ws_message(state, conn, message) {
|
|||||||
mist.Binary(_) -> {
|
mist.Binary(_) -> {
|
||||||
actor.continue(state)
|
actor.continue(state)
|
||||||
}
|
}
|
||||||
mist.Custom(_) -> {
|
mist.Custom(resp) -> {
|
||||||
|
echo resp
|
||||||
|
let resp_str =
|
||||||
|
web.encode_game_response(resp)
|
||||||
|
|> json.to_string
|
||||||
|
let assert Ok(_) = mist.send_text_frame(conn, resp_str)
|
||||||
actor.continue(state)
|
actor.continue(state)
|
||||||
}
|
}
|
||||||
mist.Closed | mist.Shutdown -> actor.Stop(process.Normal)
|
mist.Closed | mist.Shutdown -> actor.Stop(process.Normal)
|
||||||
@ -128,7 +164,7 @@ fn handle_ws_message(state, conn, message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn register_user(
|
fn register_user(
|
||||||
ctx: web.Context,
|
state: SocketState,
|
||||||
username: String,
|
username: String,
|
||||||
gamecode: String,
|
gamecode: String,
|
||||||
) -> #(String, Int) {
|
) -> #(String, Int) {
|
||||||
@ -139,40 +175,40 @@ fn register_user(
|
|||||||
|> crypto.hash_chunk(bit_array.from_string(username))
|
|> crypto.hash_chunk(bit_array.from_string(username))
|
||||||
|> crypto.hash_chunk(bit_array.from_string(gamecode))
|
|> crypto.hash_chunk(bit_array.from_string(gamecode))
|
||||||
|> crypto.digest
|
|> crypto.digest
|
||||||
|
|
||||||
let token_hash =
|
|
||||||
crypto.new_hasher(crypto.Sha512)
|
|
||||||
|> crypto.hash_chunk(token)
|
|
||||||
|> crypto.digest
|
|
||||||
|> bit_array.base64_encode(True)
|
|> bit_array.base64_encode(True)
|
||||||
|
|
||||||
|
let token_hash = player_session.hash_token(token)
|
||||||
|
|
||||||
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(ctx.player_sessions, int.to_base16(user_id))
|
let key = storail.key(state.ctx.player_sessions, int.to_base16(user_id))
|
||||||
|
|
||||||
let assert Ok(Nil) =
|
let assert Ok(Nil) =
|
||||||
storail.write(
|
storail.write(
|
||||||
key,
|
key,
|
||||||
player_session.PlayerSession(id: user_id, token_hash:, username:),
|
player_session.PlayerSession(
|
||||||
|
id: user_id,
|
||||||
|
token_hash:,
|
||||||
|
username:,
|
||||||
|
socket_id: state.id,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
let token = bit_array.base64_encode(token, True)
|
|
||||||
|
|
||||||
#(token, user_id)
|
#(token, user_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_client_msg(
|
fn handle_client_msg(
|
||||||
ctx: web.Context,
|
state: SocketState,
|
||||||
req: web.ClientRequest,
|
req: web.ClientRequest,
|
||||||
) -> web.GameResponse {
|
) -> #(SocketState, web.GameResponse) {
|
||||||
case req.msg {
|
case req.msg {
|
||||||
web.BuzzIn(time) -> {
|
web.BuzzIn(time) -> {
|
||||||
io.println("Got buzz in @ " <> float.to_string(time))
|
io.println("Got buzz in @ " <> float.to_string(time))
|
||||||
web.AckBuzzer
|
#(state, web.AckBuzzer)
|
||||||
}
|
}
|
||||||
web.Register(gamecode, username) -> {
|
web.Register(gamecode, username) -> {
|
||||||
let #(token, user_id) = register_user(ctx, username, gamecode)
|
let #(token, user_id) = register_user(state, username, gamecode)
|
||||||
let key = storail.key(ctx.sessions, gamecode)
|
let key = storail.key(state.ctx.sessions, gamecode)
|
||||||
let assert Ok(session) = storail.read(key)
|
let assert Ok(session) = storail.read(key)
|
||||||
|
|
||||||
let player = player.Player(name: username, id: user_id, score: 0)
|
let player = player.Player(name: username, id: user_id, score: 0)
|
||||||
@ -184,16 +220,54 @@ fn handle_client_msg(
|
|||||||
|
|
||||||
let assert Ok(Nil) = storail.write(key, session)
|
let assert Ok(Nil) = storail.write(key, session)
|
||||||
|
|
||||||
web.JoinResponse(username, token)
|
#(
|
||||||
|
SocketState(..state, user_id: Some(user_id), game_id: Some(gamecode)),
|
||||||
|
web.JoinResponse(username, token),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
web.CreateRoom(gamecode, username) -> {
|
web.CreateRoom(gamecode, username) -> {
|
||||||
let #(token, user_id) = register_user(ctx, username, gamecode)
|
let #(token, user_id) = register_user(state, username, gamecode)
|
||||||
|
|
||||||
let key = storail.key(ctx.sessions, gamecode)
|
let key = storail.key(state.ctx.sessions, gamecode)
|
||||||
let session = session.Session(gamecode, user_id, [])
|
let session = session.Session(gamecode, user_id, [])
|
||||||
let assert Ok(Nil) = storail.write(key, session)
|
let assert Ok(Nil) = storail.write(key, session)
|
||||||
|
|
||||||
web.HostResponse(username, token)
|
#(
|
||||||
|
SocketState(..state, user_id: Some(user_id), game_id: Some(gamecode)),
|
||||||
|
web.HostResponse(username, token),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
web.ResetBuzzers -> {
|
||||||
|
let _token_hash = player_session.hash_token(req.token)
|
||||||
|
let assert Some(user_id) = state.user_id
|
||||||
|
let assert Some(session_id) = state.game_id
|
||||||
|
|
||||||
|
let player_key =
|
||||||
|
storail.key(state.ctx.player_sessions, int.to_base16(user_id))
|
||||||
|
|
||||||
|
let assert Ok(_user) = storail.read(player_key)
|
||||||
|
|
||||||
|
let session_key = storail.key(state.ctx.sessions, session_id)
|
||||||
|
let assert Ok(session) = storail.read(session_key)
|
||||||
|
|
||||||
|
list.map(session.players, fn(player) { player.id })
|
||||||
|
|> list.map(fn(player_id) {
|
||||||
|
let player_key =
|
||||||
|
storail.key(state.ctx.player_sessions, int.to_base16(player_id))
|
||||||
|
let assert Ok(player) = storail.read(player_key)
|
||||||
|
player
|
||||||
|
})
|
||||||
|
|> list.map(fn(player) {
|
||||||
|
let assert Ok(subject) =
|
||||||
|
socket_manager.get_socket_subj_by_id(
|
||||||
|
state.ctx.socket_manager,
|
||||||
|
player.socket_id,
|
||||||
|
)
|
||||||
|
subject
|
||||||
|
})
|
||||||
|
|> list.each(fn(subject) { actor.send(subject, web.ResetBuzzer) })
|
||||||
|
|
||||||
|
#(state, web.AckBuzzer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,18 @@
|
|||||||
import config
|
import config
|
||||||
import gleam/dynamic/decode
|
import gleam/dynamic/decode
|
||||||
import gleam/erlang/process
|
|
||||||
import gleam/json
|
import gleam/json
|
||||||
import player_session
|
import player_session
|
||||||
import session
|
import session
|
||||||
|
import socket_manager
|
||||||
import storail
|
import storail
|
||||||
|
|
||||||
pub type ClientMessage {
|
pub type ClientMessage {
|
||||||
BuzzIn(time: Float)
|
BuzzIn(time: Float)
|
||||||
|
ResetBuzzers
|
||||||
Register(game_code: String, username: String)
|
Register(game_code: String, username: String)
|
||||||
CreateRoom(game_code: String, username: String)
|
CreateRoom(game_code: String, username: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode_client_message(client_message: ClientMessage) -> json.Json {
|
|
||||||
case client_message {
|
|
||||||
BuzzIn(..) ->
|
|
||||||
json.object([
|
|
||||||
#("type", json.string("buzz_in")),
|
|
||||||
#("time", json.float(client_message.time)),
|
|
||||||
])
|
|
||||||
Register(..) ->
|
|
||||||
json.object([
|
|
||||||
#("type", json.string("register")),
|
|
||||||
#("game_code", json.string(client_message.game_code)),
|
|
||||||
#("username", json.string(client_message.username)),
|
|
||||||
])
|
|
||||||
CreateRoom(..) ->
|
|
||||||
json.object([
|
|
||||||
#("type", json.string("create_room")),
|
|
||||||
#("game_code", json.string(client_message.game_code)),
|
|
||||||
#("username", json.string(client_message.username)),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn client_message_decoder() -> decode.Decoder(ClientMessage) {
|
pub fn client_message_decoder() -> decode.Decoder(ClientMessage) {
|
||||||
use variant <- decode.field("type", decode.string)
|
use variant <- decode.field("type", decode.string)
|
||||||
case variant {
|
case variant {
|
||||||
@ -51,6 +30,9 @@ pub fn client_message_decoder() -> decode.Decoder(ClientMessage) {
|
|||||||
use username <- decode.field("username", decode.string)
|
use username <- decode.field("username", decode.string)
|
||||||
decode.success(CreateRoom(game_code:, username:))
|
decode.success(CreateRoom(game_code:, username:))
|
||||||
}
|
}
|
||||||
|
"ResetBuzzers" -> {
|
||||||
|
decode.success(ResetBuzzers)
|
||||||
|
}
|
||||||
_ -> decode.failure(BuzzIn(0.0), "ClientMessage")
|
_ -> decode.failure(BuzzIn(0.0), "ClientMessage")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,6 +84,6 @@ pub type Context {
|
|||||||
config: config.Config,
|
config: config.Config,
|
||||||
sessions: storail.Collection(session.Session),
|
sessions: storail.Collection(session.Session),
|
||||||
player_sessions: storail.Collection(player_session.PlayerSession),
|
player_sessions: storail.Collection(player_session.PlayerSession),
|
||||||
selector: process.Selector(ClientRequest),
|
socket_manager: socket_manager.SocketManager(GameResponse),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import glint
|
|||||||
import mist
|
import mist
|
||||||
import player_session
|
import player_session
|
||||||
import session
|
import session
|
||||||
|
import socket_manager
|
||||||
import storail
|
import storail
|
||||||
|
|
||||||
pub type Error {
|
pub type Error {
|
||||||
@ -65,7 +66,7 @@ pub fn run_server() -> glint.Command(Result(Nil, Error)) {
|
|||||||
config: cfg,
|
config: cfg,
|
||||||
sessions: setup_session_collection(storail_cfg),
|
sessions: setup_session_collection(storail_cfg),
|
||||||
player_sessions: setup_player_session_collection(storail_cfg),
|
player_sessions: setup_player_session_collection(storail_cfg),
|
||||||
selector: process.new_selector(),
|
socket_manager: socket_manager.new(),
|
||||||
)
|
)
|
||||||
|
|
||||||
let handler = router.handle_request(_, ctx)
|
let handler = router.handle_request(_, ctx)
|
||||||
|
89
backend/src/socket_manager.gleam
Normal file
89
backend/src/socket_manager.gleam
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import gleam/dict.{type Dict}
|
||||||
|
import gleam/erlang/process.{type Subject}
|
||||||
|
import gleam/list
|
||||||
|
import gleam/otp/actor
|
||||||
|
|
||||||
|
pub type SocketManager(a) {
|
||||||
|
SocketManager(subject: Subject(SocketManagerMsg(a)))
|
||||||
|
}
|
||||||
|
|
||||||
|
type State(a) {
|
||||||
|
State(subjects: Dict(Int, Subject(a)), next_id: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SocketManagerMsg(a) {
|
||||||
|
AddSocketSubject(subject: Subject(a), client: Subject(Int))
|
||||||
|
RemoveSocketSubject(subject: Subject(a))
|
||||||
|
GetSubjectById(id: Int, client: Subject(Result(Subject(a), Nil)))
|
||||||
|
Shutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(
|
||||||
|
msg: SocketManagerMsg(a),
|
||||||
|
state: State(a),
|
||||||
|
) -> actor.Next(SocketManagerMsg(a), State(a)) {
|
||||||
|
case msg {
|
||||||
|
AddSocketSubject(subject, client) -> {
|
||||||
|
let subject_id = state.next_id
|
||||||
|
actor.send(client, subject_id)
|
||||||
|
actor.continue(State(
|
||||||
|
dict.insert(state.subjects, state.next_id, subject),
|
||||||
|
state.next_id + 1,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
RemoveSocketSubject(subject) -> {
|
||||||
|
let remove_id =
|
||||||
|
list.first(
|
||||||
|
dict.keys(
|
||||||
|
dict.filter(state.subjects, fn(_key, value) { value == subject }),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
case remove_id {
|
||||||
|
Ok(id) -> {
|
||||||
|
actor.continue(
|
||||||
|
State(..state, subjects: dict.delete(state.subjects, id)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Error(_) -> {
|
||||||
|
actor.continue(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GetSubjectById(id, client) -> {
|
||||||
|
actor.send(client, dict.get(state.subjects, id))
|
||||||
|
actor.continue(state)
|
||||||
|
}
|
||||||
|
Shutdown -> {
|
||||||
|
actor.Stop(process.Normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> SocketManager(a) {
|
||||||
|
let assert Ok(subject) = actor.start(State(dict.new(), 0), handle)
|
||||||
|
|
||||||
|
SocketManager(subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_socket_subj(
|
||||||
|
socket_manager: SocketManager(a),
|
||||||
|
subject: Subject(a),
|
||||||
|
) -> Int {
|
||||||
|
actor.call(socket_manager.subject, AddSocketSubject(subject, _), 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_socket_subj_by_id(
|
||||||
|
socket_manager: SocketManager(a),
|
||||||
|
id: Int,
|
||||||
|
) -> Result(Subject(a), Nil) {
|
||||||
|
actor.call(socket_manager.subject, GetSubjectById(id, _), 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_socket_subj(socket_manager: SocketManager(a), subject: Subject(a)) {
|
||||||
|
actor.send(socket_manager.subject, RemoveSocketSubject(subject))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown(socket_manager: SocketManager(a)) {
|
||||||
|
actor.send(socket_manager.subject, Shutdown)
|
||||||
|
}
|
@ -1,16 +1,12 @@
|
|||||||
use macroquad::{
|
use macroquad::prelude::*;
|
||||||
color::{BLACK, WHITE},
|
|
||||||
text::draw_text,
|
|
||||||
window::{clear_background, screen_height, screen_width},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::screen::Screen;
|
use crate::{model::ClientMessage, screen::Screen};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct HostScreen {}
|
pub struct HostScreen {}
|
||||||
|
|
||||||
impl Screen for HostScreen {
|
impl Screen for HostScreen {
|
||||||
fn handle_frame(&mut self, _ctx: &mut crate::context::Context) {
|
fn handle_frame(&mut self, ctx: &mut crate::context::Context) {
|
||||||
clear_background(WHITE);
|
clear_background(WHITE);
|
||||||
|
|
||||||
draw_text(
|
draw_text(
|
||||||
@ -20,6 +16,25 @@ impl Screen for HostScreen {
|
|||||||
100.0,
|
100.0,
|
||||||
BLACK,
|
BLACK,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let button_size = screen_width() * 0.01;
|
||||||
|
let button_location = Vec2::new(screen_width() * 0.50, screen_height() * 0.70);
|
||||||
|
|
||||||
|
draw_circle(
|
||||||
|
button_location.x,
|
||||||
|
button_location.y,
|
||||||
|
button_size * 1.1,
|
||||||
|
DARKBROWN,
|
||||||
|
);
|
||||||
|
draw_circle(button_location.x, button_location.y, button_size, RED);
|
||||||
|
if is_mouse_button_pressed(MouseButton::Left) {
|
||||||
|
let loc = Vec2::from(mouse_position());
|
||||||
|
let normalize = loc - button_location;
|
||||||
|
|
||||||
|
if normalize.length() < button_size {
|
||||||
|
ctx.send_msg(&ClientMessage::ResetBuzzers);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_messages(
|
fn handle_messages(
|
||||||
|
@ -11,6 +11,7 @@ pub enum ClientMessage {
|
|||||||
BuzzIn(BuzzIn),
|
BuzzIn(BuzzIn),
|
||||||
Register(Register),
|
Register(Register),
|
||||||
CreateRoom(Register),
|
CreateRoom(Register),
|
||||||
|
ResetBuzzers,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
|
@ -15,6 +15,7 @@ version = "1.0.0"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
|
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
|
||||||
gleam_json = ">= 2.3.0 and < 3.0.0"
|
gleam_json = ">= 2.3.0 and < 3.0.0"
|
||||||
|
gleam_crypto = ">= 1.5.0 and < 2.0.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gleeunit = ">= 1.0.0 and < 2.0.0"
|
gleeunit = ">= 1.0.0 and < 2.0.0"
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
# You typically do not need to edit this file
|
# You typically do not need to edit this file
|
||||||
|
|
||||||
packages = [
|
packages = [
|
||||||
|
{ name = "gleam_crypto", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "917BC8B87DBD584830E3B389CBCAB140FFE7CB27866D27C6D0FB87A9ECF35602" },
|
||||||
{ name = "gleam_json", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "C55C5C2B318533A8072D221C5E06E5A75711C129E420DD1CE463342106012E5D" },
|
{ name = "gleam_json", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "C55C5C2B318533A8072D221C5E06E5A75711C129E420DD1CE463342106012E5D" },
|
||||||
{ name = "gleam_stdlib", version = "0.59.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "F8FEE9B35797301994B81AF75508CF87C328FE1585558B0FFD188DC2B32EAA95" },
|
{ name = "gleam_stdlib", version = "0.59.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "F8FEE9B35797301994B81AF75508CF87C328FE1585558B0FFD188DC2B32EAA95" },
|
||||||
{ name = "gleeunit", version = "1.3.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "A7DD6C07B7DA49A6E28796058AA89E651D233B357D5607006D70619CD89DAAAB" },
|
{ name = "gleeunit", version = "1.3.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "A7DD6C07B7DA49A6E28796058AA89E651D233B357D5607006D70619CD89DAAAB" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[requirements]
|
[requirements]
|
||||||
|
gleam_crypto = { version = ">= 1.5.0 and < 2.0.0" }
|
||||||
gleam_json = { version = ">= 2.3.0 and < 3.0.0" }
|
gleam_json = { version = ">= 2.3.0 and < 3.0.0" }
|
||||||
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
||||||
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
import gleam/bit_array
|
||||||
|
import gleam/crypto
|
||||||
import gleam/dynamic/decode
|
import gleam/dynamic/decode
|
||||||
import gleam/json
|
import gleam/json
|
||||||
|
|
||||||
pub type PlayerSession {
|
pub type PlayerSession {
|
||||||
PlayerSession(id: Int, token_hash: String, username: String)
|
PlayerSession(id: Int, token_hash: String, username: String, socket_id: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode_player_session(player_session: PlayerSession) -> json.Json {
|
pub fn encode_player_session(player_session: PlayerSession) -> json.Json {
|
||||||
@ -10,6 +12,7 @@ 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)),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,5 +20,14 @@ 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)
|
||||||
decode.success(PlayerSession(id:, token_hash:, username:))
|
use socket_id <- decode.field("socket_id", decode.int)
|
||||||
|
decode.success(PlayerSession(id:, token_hash:, username:, socket_id:))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_token(token: String) -> String {
|
||||||
|
let token =
|
||||||
|
crypto.new_hasher(crypto.Sha512)
|
||||||
|
|> crypto.hash_chunk(bit_array.from_string(token))
|
||||||
|
|> crypto.digest
|
||||||
|
|> bit_array.base64_encode(True)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user