export class Socket {
- constructor(container) {
+ constructor(container, login_all_sockets) {
this.container = container;
+ this.login_all_sockets = login_all_sockets;
let proto = window.location.protocol === "https:" ? "wss:" : "ws:";
let uri = proto + "//" + window.location.host + "/api";
this.socket = new WebSocket(uri);
this.state = "LoginAuthResponseSent";
break;
case "LoginFailure":
- this.auth = null;
+ delete this.auth;
this.show_login(message.reason);
this.state = "Connected";
break;
case "LoginSuccess":
this.hide_login();
this.send({type: "JoinLobby", filter: ""});
- this.state = "JoinLobbySent";
+ this.state = "LoggedIn";
+ break;
+ case "JoinLobbyFailure":
+ console.error("Joining lobby failed: " + message.reason);
+ this.state = "LoggedIn";
break;
case "JoinLobbySuccess":
- this.create_game_list();
this.games = message.games;
- this.redraw_games();
+ this.create_game_list();
this.state = "InLobby";
break;
- case "AddGame":
- this.add_game(message.game);
+ case "NewGame":
+ this.new_game(message.game);
+ break;
+ case "JoinGameFailure":
+ console.error("Joining game failed: " + message.reason);
+ this.state = "LoggedIn";
+ break;
+ case "JoinGameSuccess":
+ this.game = message.game;
+ this.create_game_display();
+ this.state = "InGame";
+ break;
+ case "TakeActionSuccess":
+ this.add_action({username: this.auth.username, action: this.action});
+ if (this.action.action === "Leave") {
+ this.send({type: "JoinLobby", filter: ""});
+ }
+ delete this.action;
+ break;
+ case "TakeActionFailure":
+ console.error("Taking action failed: " + message.reason, this.action);
+ delete this.action;
+ break;
+ case "NewAction":
+ this.add_action(message.action);
break;
case "LogoutSuccess":
- this.auth = null;
+ delete this.auth;
this.show_login();
this.state = "Connected";
break;
document.getElementById("login-error").innerText = "";
}
document.getElementById("login-background").classList.remove("hidden");
- document.getElementById("login-button").onclick = login;
- document.getElementById("password-input").onchange = login;
+ document.getElementById("login-button").onclick = this.login_all_sockets;
+ document.getElementById("password-input").onchange = this.login_all_sockets;
}
game_element(game) {
console.log("getting game element for ", game);
+
var game_element = document.createElement("div");
game_element.classList.add("game-summary");
+
var id = document.createElement("div");
id.innerText = "#" + game.id;
id.classList.add("game-id");
- game_element.appendChild(id);
+ game_element.append(id);
+
var title = document.createElement("div");
- title.innerText = game.settings.format; // TODO
+ title.innerText = game.settings.title;
title.classList.add("game-title");
- game_element.appendChild(title);
+ game_element.append(title);
+
var format = document.createElement("div");
format.innerText = game.settings.format;
format.classList.add("game-format");
- game_element.appendChild(format);
+ game_element.append(format);
+
var settings = document.createElement("ul");
settings.classList.add("game-settings");
for (var setting of Object.keys(game.settings)) {
if (setting !== "id" && setting !== "title" && setting !== "format") {
var li = document.createElement("li");
li.innerText = setting + ": " + game.settings[setting];
- settings.appendChild(li);
+ settings.append(li);
}
}
- game_element.appendChild(settings);
+ game_element.append(settings);
+
+ game_element.onclick = () => this.join_game(game);
+
return game_element;
}
- add_game(game) {
+ new_game(game) {
+ var is_at_end = this.game_list_container.scrollTop + this.game_list_container.clientHeight == this.game_list_container.scrollHeight;
this.games.push(game);
var game_element = this.game_element(game);
- this.game_list.prepend(game_element);
+ this.game_list.append(game_element);
+ if (is_at_end) {
+ this.game_list_container.scrollTo({
+ top: this.game_list_container.scrollHeight - this.game_list_container.clientHeight,
+ behavior: "smooth"
+ });
+ }
+ }
+
+ join_game(game) {
+ this.send({type: "JoinGame", id: game.id});
+ this.state = "JoinGameSent";
}
create_game_list() {
this.container.textContent = "";
+ this.game_list_container = document.createElement("div");
+ this.game_list_container.classList.add("game-list-container");
this.game_list = document.createElement("div");
this.game_list.classList.add("game-list");
- this.container.append(this.game_list);
+ this.game_list_container.append(this.game_list);
+ this.container.append(this.game_list_container);
+ this.redraw_games();
}
redraw_games() {
+ this.game_list.textContent = "";
for (var game of this.games) {
var game_element = this.game_element(game);
- this.game_list.prepend(game_element);
+ this.game_list.append(game_element);
+ }
+ this.game_list_container.scrollTo({
+ top: this.game_list.clientHeight - this.container.clientHeight,
+ behavior: "auto"
+ });
+ }
+
+ take_action(action) {
+ this.action = action;
+ this.send({type: "TakeAction", action: action});
+ }
+
+ create_chatroom() {
+ this.container.textContent = "";
+ this.chatroom = document.createElement("div");
+ this.chatroom.classList.add("chatroom");
+ this.chatroom_chat = document.createElement("div");
+ this.chatroom_chat.classList.add("chatroom-chat");
+ this.chatroom.append(this.chatroom_chat);
+ this.chatroom_input = document.createElement("input");
+ this.chatroom_input.onchange = () => {
+ this.take_action({action: "Message", message: this.chatroom_input.value});
+ this.chatroom_input.value = "";
+ };
+ this.chatroom.append(this.chatroom_input);
+ var chatroom_close = document.createElement("button");
+ chatroom_close.classList.add("chatroom-close");
+ chatroom_close.innerText = "✗";
+ chatroom_close.onclick = () => this.take_action({action: "Leave"});
+ this.chatroom.append(chatroom_close);
+ this.container.append(this.chatroom);
+ this.redraw_chatroom();
+ }
+
+ join_element(username) {
+ var join_element = document.createElement("div");
+ join_element.classList.add("chatroom-join");
+ var username_element = document.createElement("div");
+ username_element.classList.add("chatroom-username");
+ username_element.innerText = username;
+ join_element.append(username_element);
+ var text_element = document.createElement("div");
+ text_element.classList.add("chatroom-text");
+ text_element.innerText = "Joined";
+ join_element.append(text_element);
+ return join_element;
+ }
+
+ message_element(username, message) {
+ var message_element = document.createElement("div");
+ message_element.classList.add("chatroom-message");
+ var username_element = document.createElement("div");
+ username_element.classList.add("chatroom-username");
+ username_element.innerText = username;
+ message_element.append(username_element);
+ var text_element = document.createElement("div");
+ text_element.classList.add("chatroom-text");
+ text_element.innerText = message;
+ message_element.append(text_element);
+ return message_element;
+ }
+
+ leave_element(username) {
+ var leave_element = document.createElement("div");
+ leave_element.classList.add("chatroom-leave");
+ var username_element = document.createElement("div");
+ username_element.classList.add("chatroom-username");
+ username_element.innerText = username;
+ leave_element.append(username_element);
+ var text_element = document.createElement("div");
+ text_element.classList.add("chatroom-text");
+ text_element.innerText = "Left";
+ leave_element.append(text_element);
+ return leave_element;
+ }
+
+ add_action(user_action) {
+ switch (this.game.summary.settings.format) {
+ case "Chatroom":
+ var is_at_end = this.chatroom_chat.scrollTop + this.chatroom_chat.clientHeight == this.chatroom_chat.scrollHeight;
+ switch (user_action.action.action) {
+ case "Join":
+ this.game.users.add(user_action.username);
+ this.chatroom_chat.append(this.join_element(user_action.username));
+ break;
+ case "Message":
+ this.chatroom_chat.append(this.message_element(user_action.username, user_action.action.message));
+ break;
+ case "Leave":
+ this.game.users.delete(user_action.username);
+ this.chatroom_chat.append(this.leave_element(user_action.username));
+ break;
+ default:
+ console.error("Unknown action for chatroom", user_action);
+ break;
+ }
+ if (is_at_end) {
+ this.chatroom_chat.scrollTo({
+ top: this.chatroom_chat.scrollHeight - this.chatroom_chat.clientHeight,
+ behavior: "smooth"
+ });
+ }
+ break;
+ }
+ }
+
+ redraw_chatroom() {
+ this.chatroom_chat.textContent = "";
+ for (var user_action of this.game.state.actions) {
+ this.add_action(user_action);
+ }
+ }
+
+ create_game_display() {
+ this.container.textContent = "";
+ switch (this.game.summary.settings.format) {
+ case "Chatroom":
+ this.game.users = new Set();
+ this.create_chatroom();
+ if (!this.game.users.has(this.auth.username)) {
+ this.take_action({action: "Join", seat: 0, chips: 0});
+ }
+ break;
}
}
}
pub async fn apply_message(&mut self, message: ClientMessage) -> ServerMessage {
+ let interests_before = self.interests();
+ let response = self.message_response(message).await;
+ let interests_after = self.interests();
+ if interests_before != interests_after {
+ debug!("Client interests changed from {:?} to {:?}", interests_before, interests_after);
+ self.server.register_interests(interests_after).await;
+ }
+ response
+ }
+
+ async fn message_response(&mut self, message: ClientMessage) -> ServerMessage {
match (&mut self.client, message) {
(_, ClientMessage::CreateUser{username, auth, nickname}) => {
match self.server.create_user(&username, auth, &nickname).await {
(ClientState::LoginAuthIssued{username, challenge}, ClientMessage::LoginAuthResponse{signature}) => {
if self.server.verify(&username, &challenge, &signature).await {
self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::Idle};
- self.server.register_interests(self.interests()).await;
ServerMessage::LoginSuccess
} else {
self.client = ClientState::Connected;
ServerMessage::LoginFailure{reason: "Invalid username or password".to_string()}
}
}
- (ClientState::LoggedIn{username, state: LoggedInState::Idle}, ClientMessage::JoinLobby{filter}) => {
+ (ClientState::LoggedIn{username, ..}, ClientMessage::JoinLobby{filter}) => {
let mut game_list = GameList::new(filter);
match self.server.update_game_list(&mut game_list).await {
Ok(()) => {
Ok(game) => {
let game_view = game.view_for(&username);
self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::InGame{game}};
- self.server.register_interests(self.interests()).await;
ServerMessage::JoinGameSuccess{game: game_view}
}
Err(err) => ServerMessage::JoinGameFailure{reason: err.to_string()},
}
(ClientState::LoggedIn{username, state: LoggedInState::InLobby{..}}, ClientMessage::LeaveLobby) => {
self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::Idle};
- self.server.register_interests(self.interests()).await;
ServerMessage::LeaveGameSuccess
}
(ClientState::LoggedIn{username, state: LoggedInState::InGame{..}}, ClientMessage::LeaveGame) => {
self.client = ClientState::LoggedIn{username: username.clone(), state: LoggedInState::Idle};
- self.server.register_interests(self.interests()).await;
ServerMessage::LeaveGameSuccess
}
(ClientState::LoggedIn{..}, ClientMessage::Logout) => {
self.client = ClientState::Connected;
- self.server.register_interests(self.interests()).await;
ServerMessage::LogoutSuccess
}
(ClientState::LoggedIn{username, ..}, ClientMessage::ChangeAuth{auth}) => {