Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3d10f31ac | ||
| e4384ee894 | |||
|
|
d667a2f68c | ||
| 35f608513d | |||
|
|
13038b0cb9 | ||
| b17aae5795 |
15
CHANGELOG.md
15
CHANGELOG.md
@@ -244,3 +244,18 @@
|
|||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **api:** FRO-14 Create Game ([#107](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/107)) ([bd7a055](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/bd7a055a0944a1c5219f21bb080bf658229f49e9))
|
* **api:** FRO-14 Create Game ([#107](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/107)) ([bd7a055](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/bd7a055a0944a1c5219f21bb080bf658229f49e9))
|
||||||
|
## (2025-12-11)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* FRO-31 Small backend changes ([#108](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/108)) ([b17aae5](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/b17aae5795b35ce3805db87c9bf741a5a96cd5ac))
|
||||||
|
## (2025-12-14)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Update routing and websocket configuration for game state management ([#109](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/109)) ([35f6085](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/35f608513dd80eece46d49b40ecf31c8e915d307))
|
||||||
|
## (2025-12-18)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **ui:** FRO-36 PWA ([#110](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/110)) ([e4384ee](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/e4384ee8945ac462fe1f3580215117e0a438f71a))
|
||||||
|
|||||||
Submodule knockoutwhistfrontend updated: 0b8a1794a0...30798ce723
@@ -34,7 +34,7 @@ class StatusController @Inject()(
|
|||||||
Json.obj(
|
Json.obj(
|
||||||
"status" -> "authenticated",
|
"status" -> "authenticated",
|
||||||
"username" -> user.name,
|
"username" -> user.name,
|
||||||
"inGame" -> "false"
|
"inGame" -> false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -43,7 +43,7 @@ class StatusController @Inject()(
|
|||||||
Json.obj(
|
Json.obj(
|
||||||
"status" -> "authenticated",
|
"status" -> "authenticated",
|
||||||
"username" -> user.name,
|
"username" -> user.name,
|
||||||
"inGame" -> "true",
|
"inGame" -> true,
|
||||||
"gameId" -> game.id
|
"gameId" -> game.id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -80,7 +80,7 @@ class StatusController @Inject()(
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
private def getUserFromSession(request: RequestHeader): Option[User] = {
|
private def getUserFromSession(request: RequestHeader): Option[User] = {
|
||||||
val session = request.cookies.get("sessionId")
|
val session = request.cookies.get("accessToken")
|
||||||
if (session.isDefined)
|
if (session.isDefined)
|
||||||
return sessionManager.getUserBySession(session.get.value)
|
return sessionManager.getUserBySession(session.get.value)
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class WebsocketController @Inject()(
|
|||||||
)(implicit system: ActorSystem, mat: Materializer) extends AbstractController(cc) {
|
)(implicit system: ActorSystem, mat: Materializer) extends AbstractController(cc) {
|
||||||
|
|
||||||
def socket(): WebSocket = WebSocket.accept[String, String] { request =>
|
def socket(): WebSocket = WebSocket.accept[String, String] { request =>
|
||||||
val session = request.cookies.get("sessionId")
|
val session = request.cookies.get("accessToken")
|
||||||
if (session.isEmpty) throw new Exception("No session cookie found")
|
if (session.isEmpty) throw new Exception("No session cookie found")
|
||||||
val userOpt = sessionManger.getUserBySession(session.get.value)
|
val userOpt = sessionManger.getUserBySession(session.get.value)
|
||||||
if (userOpt.isEmpty) throw new Exception("Invalid session")
|
if (userOpt.isEmpty) throw new Exception("Invalid session")
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package dto.subDTO
|
|||||||
|
|
||||||
import de.knockoutwhist.cards.Hand
|
import de.knockoutwhist.cards.Hand
|
||||||
|
|
||||||
case class HandDTO(card: List[CardDTO])
|
case class HandDTO(cards: List[CardDTO])
|
||||||
|
|
||||||
object HandDTO {
|
object HandDTO {
|
||||||
|
|
||||||
def apply(hand: Hand): HandDTO = {
|
def apply(hand: Hand): HandDTO = {
|
||||||
HandDTO(
|
HandDTO(
|
||||||
card = hand.cards.zipWithIndex.map { case (card, idx) => CardDTO(card, idx) }
|
cards = hand.cards.zipWithIndex.map { case (card, idx) => CardDTO(card, idx) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ object TrickDTO {
|
|||||||
|
|
||||||
def apply(trick: Trick): TrickDTO = {
|
def apply(trick: Trick): TrickDTO = {
|
||||||
TrickDTO(
|
TrickDTO(
|
||||||
cards = trick.cards.map { case (card, player) => player.id.toString -> CardDTO(card) },
|
cards = trick.cards.map { case (card, player) => player.name -> CardDTO(card) },
|
||||||
firstCard = trick.firstCard.map(card => CardDTO(card)),
|
firstCard = trick.firstCard.map(card => CardDTO(card)),
|
||||||
winner = trick.winner.map(player => PlayerDTO(player))
|
winner = trick.winner.map(player => PlayerDTO(player))
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -46,13 +46,13 @@ class UserSession(val user: User, val host: Boolean, val gameLobby: GameLobby) e
|
|||||||
case "StartGame" =>
|
case "StartGame" =>
|
||||||
gameLobby.startGame(user)
|
gameLobby.startGame(user)
|
||||||
case "PlayCard" =>
|
case "PlayCard" =>
|
||||||
val maybeCardIndex: Option[String] = (data \ "cardindex").asOpt[String]
|
val maybeCardIndex: Option[Int] = (data \ "cardindex").asOpt[Int]
|
||||||
maybeCardIndex match {
|
maybeCardIndex match {
|
||||||
case Some(index) =>
|
case Some(index) =>
|
||||||
val session = gameLobby.getUserSession(user.id)
|
val session = gameLobby.getUserSession(user.id)
|
||||||
gameLobby.playCard(session, index.toInt)
|
gameLobby.playCard(session, index)
|
||||||
case None =>
|
case None =>
|
||||||
println("Card Index not found or is not a number.")
|
println("Card Index not found or is not a number." + data)
|
||||||
}
|
}
|
||||||
case "PickTrumpsuit" =>
|
case "PickTrumpsuit" =>
|
||||||
val maybeSuitIndex: Option[Int] = (data \ "suitIndex").asOpt[Int]
|
val maybeSuitIndex: Option[Int] = (data \ "suitIndex").asOpt[Int]
|
||||||
@@ -74,6 +74,10 @@ class UserSession(val user: User, val host: Boolean, val gameLobby: GameLobby) e
|
|||||||
}
|
}
|
||||||
case "ReturnToLobby" =>
|
case "ReturnToLobby" =>
|
||||||
gameLobby.returnToLobby(this)
|
gameLobby.returnToLobby(this)
|
||||||
|
case "LeaveGame" =>
|
||||||
|
gameLobby.leaveGame(user.id, false)
|
||||||
|
case _ =>
|
||||||
|
println("Unknown event type: " + eventType + " with data: " + data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ object WebsocketEventMapper {
|
|||||||
Json.obj(
|
Json.obj(
|
||||||
"id" -> ("request-" + java.util.UUID.randomUUID().toString),
|
"id" -> ("request-" + java.util.UUID.randomUUID().toString),
|
||||||
"event" -> obj.id,
|
"event" -> obj.id,
|
||||||
"state" -> stateToJson(session),
|
"state" -> session.gameLobby.getLogic.getCurrentState.toString,
|
||||||
|
"stateData" -> stateToJson(session),
|
||||||
"data" -> data
|
"data" -> data
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ auth {
|
|||||||
play.filters.enabled += "play.filters.cors.CORSFilter"
|
play.filters.enabled += "play.filters.cors.CORSFilter"
|
||||||
|
|
||||||
play.filters.cors {
|
play.filters.cors {
|
||||||
allowedOrigins = ["http://localhost:5173"]
|
allowedOrigins = ["http://localhost:5173", "http://localhost:3000"]
|
||||||
allowedCredentials = true
|
allowedCredentials = true
|
||||||
allowedHttpMethods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
|
allowedHttpMethods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
|
||||||
allowedHttpHeaders = ["Accept", "Content-Type", "Origin", "X-Requested-With"]
|
allowedHttpHeaders = ["Accept", "Content-Type", "Origin", "X-Requested-With"]
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
// javascript
|
let ws = null;
|
||||||
let ws = null; // will be created by connectWebSocket()
|
const pending = new Map();
|
||||||
const pending = new Map(); // id -> { resolve, reject, timer }
|
const handlers = new Map();
|
||||||
const handlers = new Map(); // eventType -> handler(data) -> (value|Promise)
|
|
||||||
|
|
||||||
let timer = null;
|
let timer = null;
|
||||||
|
|
||||||
// helper to attach message/error/close handlers to a socket
|
|
||||||
function setupSocketHandlers(socket) {
|
function setupSocketHandlers(socket) {
|
||||||
socket.onmessage = (event) => {
|
socket.onmessage = (event) => {
|
||||||
console.debug("SERVER MESSAGE:", event.data);
|
console.debug("SERVER MESSAGE:", event.data);
|
||||||
@@ -48,7 +46,6 @@ function setupSocketHandlers(socket) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
// no handler: respond with an error object in data so server can fail it
|
|
||||||
console.warn("No handler for event:", eventType);
|
console.warn("No handler for event:", eventType);
|
||||||
sendResponse({error: "No handler for event: " + eventType});
|
sendResponse({error: "No handler for event: " + eventType});
|
||||||
return;
|
return;
|
||||||
@@ -91,7 +88,6 @@ function setupSocketHandlers(socket) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// connect/disconnect helpers
|
|
||||||
function connectWebSocket(url = null) {
|
function connectWebSocket(url = null) {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
const loc = window.location;
|
const loc = window.location;
|
||||||
@@ -100,7 +96,6 @@ function connectWebSocket(url = null) {
|
|||||||
}
|
}
|
||||||
if (ws && ws.readyState === WebSocket.OPEN) return Promise.resolve();
|
if (ws && ws.readyState === WebSocket.OPEN) return Promise.resolve();
|
||||||
if (ws && ws.readyState === WebSocket.CONNECTING) {
|
if (ws && ws.readyState === WebSocket.CONNECTING) {
|
||||||
// already connecting - return a promise that resolves on open
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const prevOnOpen = ws.onopen;
|
const prevOnOpen = ws.onopen;
|
||||||
const prevOnError = ws.onerror;
|
const prevOnError = ws.onerror;
|
||||||
@@ -121,7 +116,6 @@ function connectWebSocket(url = null) {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
console.log("WebSocket connection established!");
|
console.log("WebSocket connection established!");
|
||||||
// start heartbeat
|
|
||||||
timer = setInterval(() => {
|
timer = setInterval(() => {
|
||||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||||
sendEventAndWait("ping", {}).then(
|
sendEventAndWait("ping", {}).then(
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=4
|
MAJOR=4
|
||||||
MINOR=13
|
MINOR=16
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
Reference in New Issue
Block a user