Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/api/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func (handler *V1Handler) SetupRoutes(rootPath string, register func(string, str
// We support both path parameter and cookie.
register("GET", path.Join(v1, "lobby", "ws"), handler.websocketUpgrade)

register("POST", path.Join(v1, "lobby", "resurrect"), handler.resurrectLobby)
register("POST", path.Join(v1, "lobby", "{lobby_id}", "player"), handler.postPlayer)
}

Expand Down
26 changes: 24 additions & 2 deletions internal/api/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package api

import (
"encoding/base64"
json "encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -63,7 +64,6 @@ type LobbyEntry struct {
func (handler *V1Handler) getLobbies(writer http.ResponseWriter, _ *http.Request) {
// REMARK: If paging is ever implemented, we might want to maintain order
// when deleting lobbies from state in the state package.

lobbies := state.GetPublicLobbies()
lobbyEntries := make(LobbyEntries, 0, len(lobbies))
for _, lobby := range lobbies {
Expand All @@ -80,7 +80,7 @@ func (handler *V1Handler) getLobbies(writer http.ResponseWriter, _ *http.Request
MaxClientsPerIP: lobby.ClientsPerIPLimit,
Wordpack: lobby.Wordpack,
State: lobby.State,
Scoring: lobby.ScoreCalculation.Identifier(),
Scoring: lobby.ScoreCalculationIdentifier,
})
}

Expand All @@ -92,6 +92,28 @@ func (handler *V1Handler) getLobbies(writer http.ResponseWriter, _ *http.Request
}
}

func (handler *V1Handler) resurrectLobby(writer http.ResponseWriter, request *http.Request) {
var data game.LobbyRestoreData
base64Decoder := base64.NewDecoder(base64.StdEncoding, request.Body)
if err := json.NewDecoder(base64Decoder).Decode(&data); err != nil {
log.Println("Error unmarshalling lobby resurrection data:", err)
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}

lobby := data.Lobby
// We add the lobby, while the lobby mutex is aqcuired. This prevents us
// from attempting to connect to the lobby, before the internal state has
// been restored correctly.
lobby.Synchronized(func() {
if state.ResurrectLobby(lobby) {
lobby.WriteObject = WriteObject
lobby.WritePreparedMessage = WritePreparedMessage
lobby.ResurrectUnsynchronized(&data)
}
})
}

func (handler *V1Handler) postLobby(writer http.ResponseWriter, request *http.Request) {
if err := request.ParseForm(); err != nil {
http.Error(writer, err.Error(), http.StatusBadRequest)
Expand Down
7 changes: 6 additions & 1 deletion internal/api/ws.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ func (handler *V1Handler) websocketUpgrade(writer http.ResponseWriter, request *

lobby := state.GetLobby(lobbyId)
if lobby == nil {
http.Error(writer, ErrLobbyNotExistent.Error(), http.StatusNotFound)
socket, err := upgrader.Upgrade(writer, request)
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}
socket.WriteClose(1000, []byte("lobby_gone"))
return
}

Expand Down
40 changes: 40 additions & 0 deletions internal/frontend/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,46 @@
const discordInstanceId = getCookie("discord-instance-id")
const rootPath = `${discordInstanceId ? ".proxy/" : ""}{{.RootPath}}`

function createLabel(cssClass, forElement, text) {
const label = document.createElement("label");
label.setAttribute("for", forElement);
label.classList.add(cssClass);
label.innerText = text;
return label;
}

function createNumberInput(id, min, max, value) {
const input = document.createElement("input");
input.setAttribute("type", "number");
// Figure out why I did this exactly.
input.setAttribute("size", "4");
input.setAttribute("id", id);
input.setAttribute("name", id);
input.setAttribute("value", Number.toString(value));
input.setAttribute("min", Number.toString(min));
input.setAttribute("max", Number.toString(max));

const decButton = document.createElement("button");
decButton.setAttribute("type", "button");
decButton.classList.add("number-decrement");
decButton.addEventListener("click", function() {
input.stepDown();
})
decButton.innerText = "-";

const incButton = document.createElement("button");
incButton.setAttribute("type", "button");
incButton.classList.add("number-increment");
incButton.addEventListener("click", function() {
input.stepUp();
})
incButton.innerText = "+";

const div = document.createElement("div")
div.append(decButton, input, incButton);
return div;
}

Array
.from(document.getElementsByClassName("number-input"))
.forEach(number_input => {
Expand Down
69 changes: 55 additions & 14 deletions internal/frontend/lobby.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,73 @@ let hasSocketEverConnected = false;
let socket;
function connectToWebsocket() {
if (socketIsConnecting === true) {
console.log("aborting connection attempt.");
return;
}

socketIsConnecting = true;

socket = new WebSocket(`${rootPath}/v1/lobby/ws`);
try {
socket = new WebSocket(`${rootPath}/v1/lobby/ws`);
} catch (exception) {
console.log("Connection error:" + exception)
socketIsConnecting = false;
connectToWebsocket();
return;
}

socket.onerror = error => {
//Is not connected and we haven't yet said that we are done trying to
//connect, this means that we could never even establish a connection.
if (socket.readyState != 1 && !hasSocketEverConnected) {
if (socket.readyState != 1) {
socketIsConnecting = false;
showTextDialog("connection-error-dialog",
'{{.Translation.Get "error-connecting"}}',
`{{.Translation.Get "error-connecting-text"}}`);
console.log("Error establishing connection: ", error);
if (!hasSocketEverConnected) {
showTextDialog("connection-error-dialog",
'{{.Translation.Get "error-connecting"}}',
`{{.Translation.Get "error-connecting-text"}}`);
console.log("Error establishing connection: ", error);
} else {
connectToWebsocket();
}
} else {
console.log("Socket error: ", error)
}
};

socket.onopen = () => {
closeDialog(shutdownDialogId);
closeDialog(reconnectDialogId);

hasSocketEverConnected = true;
socketIsConnecting = false;

socket.onclose = event => {
//We want to avoid handling the error multiple times and showing the incorrect dialogs.
//We w to avoid handling the error multiple times and showing the incorrect dialogs.
socket.onerror = null;

console.log("Socket Closed Connection: ", event);
console.log("Attempting to reestablish socket connection.");
showReconnectDialogIfNotShown();
connectToWebsocket();

if (restoreData && event.reason === "lobby_gone") {
console.log("Resurrecting lobby ...",);
fetch('/v1/lobby/resurrect', {
method: 'POST',
body: restoreData,
}).then(() => {
console.log("Attempting to reestablish socket connection after resurrection ...");
socketIsConnecting = false;
connectToWebsocket();
});

return
}

if (event.reason !== "lobby_gone" && event.reason !== "server_restart") {
console.log("Attempting to reestablish socket connection.");
showReconnectDialogIfNotShown();
}
if (event.reason === "server_restart") {
connectToWebsocket();
}
};

registerMessageHandler(socket);
Expand All @@ -53,6 +85,7 @@ function connectToWebsocket() {
};
}

const shutdownDialogId = "shutdown-dialog";
const reconnectDialogId = "reconnect-dialog";
function showReconnectDialogIfNotShown() {
const previousReconnectDialog = document.getElementById(reconnectDialogId);
Expand Down Expand Up @@ -833,6 +866,7 @@ let rounds = 0;
let roundEndTime = 0;
let gameState = "unstarted";
let drawingTimeSetting = "∞";
let restoreData;

function registerMessageHandler(targetSocket) {
targetSocket.onmessage = event => {
Expand Down Expand Up @@ -985,10 +1019,16 @@ function registerMessageHandler(targetSocket) {
+ '{{.Translation.Get "custom-words-per-turn-setting"}}: ' + parsed.data.customWordsPerTurn + "%\n"
+ '{{.Translation.Get "players-per-ip-limit-setting"}}: ' + parsed.data.clientsPerIpLimit);
} else if (parsed.type === "shutdown") {
socket.onclose = null;
socket.close();
showDialog("shutdown-info", "Server shutting down",
document.createTextNode("Sorry, but the server is about to shut down. Please come back at a later time."));
console.log("Shutdown event received");
if (parsed.data) {
restoreData = parsed.data;
// FIXMe Text anpassen!
showDialog("shutdown-dialog", "Server shutting down",
document.createTextNode("Sorry, but the server is about to shut down. Attempting to restore lobby on restart ..."));
} else {
showDialog("shutdown-dialog", "Server shutting down",
document.createTextNode("Sorry, but the server is about to shut down. Please come back at a later time."));
}
}
}
};
Expand Down Expand Up @@ -1033,6 +1073,7 @@ function setRoundTimeLeft(timeLeftMs) {
}

function handleReadyEvent(ready) {
restoreData = null;
ownerID = ready.ownerId;
ownID = ready.playerId;

Expand Down
Loading