feat(websocket)!: Implement WebSocket connection and event handling

This commit is contained in:
2025-11-22 18:32:28 +01:00
parent 09cc96141d
commit ba373f91e9
25 changed files with 420 additions and 80 deletions

View File

@@ -2,13 +2,19 @@ package model.sessions
import de.knockoutwhist.events.player.{RequestCardEvent, RequestTieChoiceEvent, RequestTrumpSuitEvent}
import de.knockoutwhist.utils.events.SimpleEvent
import logic.game.GameLobby
import model.users.User
import org.apache.pekko.actor.{Actor, ActorRef}
import play.api.libs.json.{JsObject, JsValue, Json}
import util.WebsocketEventMapper
import java.util.UUID
import java.util.concurrent.locks.{Lock, ReentrantLock}
import java.util.concurrent.locks.ReentrantLock
import scala.util.{Failure, Success, Try}
class UserSession(val user: User, val host: Boolean) extends PlayerSession {
class UserSession(val user: User, val host: Boolean, val gameLobby: GameLobby) extends PlayerSession {
var canInteract: Option[InteractionType] = None
var websocketActor: Option[UserWebsocketActor] = None
val lock: ReentrantLock = ReentrantLock()
override def updatePlayer(event: SimpleEvent): Unit = {
@@ -31,5 +37,17 @@ class UserSession(val user: User, val host: Boolean) extends PlayerSession {
def resetCanInteract(): Unit = {
canInteract = None
}
def handleWebResponse(eventType: String, data: JsObject): Unit = {
lock.lock()
Try {
eventType match {
case "Ping" =>
// No action needed for Ping
()
}
}
lock.unlock()
}
}

View File

@@ -0,0 +1,94 @@
package model.sessions
import de.knockoutwhist.utils.events.SimpleEvent
import org.apache.pekko.actor.{Actor, ActorRef}
import play.api.libs.json.{JsObject, JsValue, Json}
import util.WebsocketEventMapper
import scala.util.{Failure, Success, Try}
class UserWebsocketActor(
out: ActorRef,
session: UserSession
) extends Actor {
if (session.websocketActor.isDefined) {
session.websocketActor.foreach(actor => actor.transmitTextToClient("Error: Multiple websocket connections detected. Closing this connection."))
context.stop(self)
} else {
session.websocketActor = Some(this)
}
override def receive: Receive = {
case msg: String =>
val jsonObject = Try {
Json.parse(msg)
}
Try {
jsonObject match {
case Success(value) =>
handle(value)
case Failure(exception) =>
transmitTextToClient(s"Error parsing JSON: ${exception.getMessage}")
}
}.failed.foreach(
ex => transmitTextToClient(s"Error handling message: ${ex.getMessage}")
)
case other =>
}
def transmitEventToClient(event: SimpleEvent): Unit = {
val jsonString = WebsocketEventMapper.toJsonString(event)
out ! jsonString
}
private def transmitJsonToClient(jsonObj: JsObject): Unit = {
out ! jsonObj.toString()
}
private def transmitTextToClient(text: String): Unit = {
out ! text
}
private def handle(json: JsValue): Unit = {
val idOpt = (json \ "id").asOpt[String]
if (idOpt.isEmpty) {
transmitJsonToClient(Json.obj(
"status" -> "error",
"error" -> "Missing 'id' field"
))
return
}
val id = idOpt.get
val eventOpt = (json \ "event").asOpt[String]
if (eventOpt.isEmpty) {
transmitJsonToClient(Json.obj(
"id" -> id,
"event" -> null,
"status" -> "error",
"error" -> "Missing 'event' field"
))
return
}
val event = eventOpt.get
val data = (json \ "data").asOpt[JsObject].getOrElse(Json.obj())
val result = Try {
session.handleWebResponse(event, data)
}
if (result.isSuccess) {
transmitJsonToClient(Json.obj(
"id" -> id,
"event" -> event,
"status" -> "success"
))
} else {
transmitJsonToClient(Json.obj(
"id" -> id,
"event" -> event,
"status" -> "error",
"error" -> result.failed.get.getMessage
))
}
}
}