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