Compare commits
1 Commits
4.3.0
...
25c3c61c8c
| Author | SHA1 | Date | |
|---|---|---|---|
| 25c3c61c8c |
@@ -154,8 +154,3 @@
|
|||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **api:** Implemented card played event via websocket ([#85](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/85)) ([3c0828f](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/3c0828fdbeb507706b86f1662476c46e760533e4))
|
* **api:** Implemented card played event via websocket ([#85](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/85)) ([3c0828f](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/3c0828fdbeb507706b86f1662476c46e760533e4))
|
||||||
## (2025-11-26)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **api:** Implemented session closed and kick event via websocket ([#87](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/87)) ([1ef5e8a](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/1ef5e8a72fdf8a3d1ae624c8c3d7c6595017bc6f))
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class IngameController @Inject()(
|
|||||||
val game = PodManager.getGame(gameId)
|
val game = PodManager.getGame(gameId)
|
||||||
val playerToKickUUID = UUID.fromString(playerToKick)
|
val playerToKickUUID = UUID.fromString(playerToKick)
|
||||||
val result = Try {
|
val result = Try {
|
||||||
game.get.leaveGame(playerToKickUUID, true)
|
game.get.leaveGame(playerToKickUUID)
|
||||||
}
|
}
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
Ok(Json.obj(
|
Ok(Json.obj(
|
||||||
@@ -107,7 +107,7 @@ class IngameController @Inject()(
|
|||||||
def leaveGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
def leaveGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||||
val game = PodManager.getGame(gameId)
|
val game = PodManager.getGame(gameId)
|
||||||
val result = Try {
|
val result = Try {
|
||||||
game.get.leaveGame(request.user.id, false)
|
game.get.leaveGame(request.user.id)
|
||||||
}
|
}
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
Ok(Json.obj(
|
Ok(Json.obj(
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package events
|
|
||||||
|
|
||||||
import model.users.User
|
|
||||||
|
|
||||||
case class KickEvent(user: User) extends UserEvent(user) {
|
|
||||||
|
|
||||||
override def id: String = "KickEvent"
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package events
|
|
||||||
|
|
||||||
import model.users.User
|
|
||||||
|
|
||||||
case class LeftEvent(user: User) extends UserEvent(user) {
|
|
||||||
|
|
||||||
override def id: String = "LeftEvent"
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package events
|
|
||||||
|
|
||||||
import de.knockoutwhist.utils.events.SimpleEvent
|
|
||||||
|
|
||||||
case class LobbyUpdateEvent() extends SimpleEvent {
|
|
||||||
|
|
||||||
override def id: String = "LobbyUpdateEvent"
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package events
|
|
||||||
|
|
||||||
import de.knockoutwhist.utils.events.SimpleEvent
|
|
||||||
import model.users.User
|
|
||||||
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
abstract class UserEvent(user: User) extends SimpleEvent {
|
|
||||||
|
|
||||||
def userId: UUID = user.id
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -10,14 +10,13 @@ import de.knockoutwhist.player.Playertype.HUMAN
|
|||||||
import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory}
|
import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory}
|
||||||
import de.knockoutwhist.rounds.{Match, Round, Trick}
|
import de.knockoutwhist.rounds.{Match, Round, Trick}
|
||||||
import de.knockoutwhist.utils.events.{EventListener, SimpleEvent}
|
import de.knockoutwhist.utils.events.{EventListener, SimpleEvent}
|
||||||
import events.{KickEvent, LeftEvent, LobbyUpdateEvent, UserEvent}
|
|
||||||
import exceptions.*
|
import exceptions.*
|
||||||
import logic.PodManager
|
import logic.PodManager
|
||||||
import model.sessions.{InteractionType, UserSession}
|
import model.sessions.{InteractionType, UserSession}
|
||||||
import model.users.User
|
import model.users.User
|
||||||
import play.api.libs.json.{JsObject, Json}
|
import play.api.libs.json.{JsObject, Json}
|
||||||
|
|
||||||
import java.util.{Timer, TimerTask, UUID}
|
import java.util.UUID
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
import scala.collection.mutable.ListBuffer
|
import scala.collection.mutable.ListBuffer
|
||||||
|
|
||||||
@@ -45,7 +44,7 @@ class GameLobby private(
|
|||||||
)
|
)
|
||||||
users += (user.id -> userSession)
|
users += (user.id -> userSession)
|
||||||
PodManager.registerUserToGame(user, id)
|
PodManager.registerUserToGame(user, id)
|
||||||
logic.invoke(LobbyUpdateEvent())
|
//TODO : transmit Lobby Update transmitToAll()
|
||||||
userSession
|
userSession
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,8 +52,6 @@ class GameLobby private(
|
|||||||
event match {
|
event match {
|
||||||
case event: PlayerEvent =>
|
case event: PlayerEvent =>
|
||||||
users.get(event.playerId).foreach(session => session.updatePlayer(event))
|
users.get(event.playerId).foreach(session => session.updatePlayer(event))
|
||||||
case event: UserEvent =>
|
|
||||||
users.get(event.userId).foreach(session => session.updatePlayer(event))
|
|
||||||
case event: GameStateChangeEvent =>
|
case event: GameStateChangeEvent =>
|
||||||
if (event.oldState == MainMenu && event.newState == Lobby) {
|
if (event.oldState == MainMenu && event.newState == Lobby) {
|
||||||
return
|
return
|
||||||
@@ -96,9 +93,8 @@ class GameLobby private(
|
|||||||
* Remove the user from the game lobby.
|
* Remove the user from the game lobby.
|
||||||
*
|
*
|
||||||
* @param user the user who wants to leave the game.
|
* @param user the user who wants to leave the game.
|
||||||
* @param kicked whether the user was kicked or left voluntarily.
|
|
||||||
*/
|
*/
|
||||||
def leaveGame(userId: UUID, kicked: Boolean): Unit = {
|
def leaveGame(userId: UUID): Unit = {
|
||||||
val sessionOpt = users.get(userId)
|
val sessionOpt = users.get(userId)
|
||||||
if (sessionOpt.isEmpty) {
|
if (sessionOpt.isEmpty) {
|
||||||
throw new NotInThisGameException("You are not in this game!")
|
throw new NotInThisGameException("You are not in this game!")
|
||||||
@@ -109,14 +105,16 @@ class GameLobby private(
|
|||||||
PodManager.removeGame(id)
|
PodManager.removeGame(id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (kicked) {
|
sessionOpt.get.websocketActor.foreach(act => act.transmitJsonToClient(Json.obj(
|
||||||
logic.invoke(KickEvent(sessionOpt.get.user))
|
"id" -> "-1",
|
||||||
} else {
|
"event" -> "SessionClosed",
|
||||||
logic.invoke(LeftEvent(sessionOpt.get.user))
|
"data" -> Json.obj(
|
||||||
}
|
"reason" -> "You left the game (or got kicked)."
|
||||||
|
)
|
||||||
|
)))
|
||||||
users.remove(userId)
|
users.remove(userId)
|
||||||
PodManager.unregisterUserFromGame(sessionOpt.get.user)
|
PodManager.unregisterUserFromGame(sessionOpt.get.user)
|
||||||
logic.invoke(LobbyUpdateEvent())
|
//TODO: transmit Lobby Update transmitToAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import model.sessions.UserSession
|
|||||||
import play.api.libs.json.{JsValue, Json}
|
import play.api.libs.json.{JsValue, Json}
|
||||||
import tools.jackson.databind.json.JsonMapper
|
import tools.jackson.databind.json.JsonMapper
|
||||||
import tools.jackson.module.scala.ScalaModule
|
import tools.jackson.module.scala.ScalaModule
|
||||||
import util.mapper.{CardPlayedEventMapper, GameStateEventMapper, KickEventMapper, LeftEventMapper, LobbyUpdateEventMapper, ReceivedHandEventMapper, SessionClosedMapper, SimpleEventMapper}
|
import util.mapper.{CardPlayedEventMapper, GameStateEventMapper, ReceivedHandEventMapper, SimpleEventMapper}
|
||||||
|
|
||||||
object WebsocketEventMapper {
|
object WebsocketEventMapper {
|
||||||
|
|
||||||
@@ -27,10 +27,6 @@ object WebsocketEventMapper {
|
|||||||
registerCustomMapper(ReceivedHandEventMapper)
|
registerCustomMapper(ReceivedHandEventMapper)
|
||||||
registerCustomMapper(GameStateEventMapper)
|
registerCustomMapper(GameStateEventMapper)
|
||||||
registerCustomMapper(CardPlayedEventMapper)
|
registerCustomMapper(CardPlayedEventMapper)
|
||||||
registerCustomMapper(LobbyUpdateEventMapper)
|
|
||||||
registerCustomMapper(LeftEventMapper)
|
|
||||||
registerCustomMapper(KickEventMapper)
|
|
||||||
registerCustomMapper(SessionClosedMapper)
|
|
||||||
|
|
||||||
def toJson(obj: SimpleEvent, session: UserSession): JsValue = {
|
def toJson(obj: SimpleEvent, session: UserSession): JsValue = {
|
||||||
val data: Option[JsValue] = if (customMappers.contains(obj.id)) {
|
val data: Option[JsValue] = if (customMappers.contains(obj.id)) {
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package util.mapper
|
|
||||||
|
|
||||||
import controllers.routes
|
|
||||||
import events.KickEvent
|
|
||||||
import model.sessions.UserSession
|
|
||||||
import play.api.libs.json.{JsObject, Json}
|
|
||||||
|
|
||||||
object KickEventMapper extends SimpleEventMapper[KickEvent] {
|
|
||||||
|
|
||||||
override def id: String = "KickEvent"
|
|
||||||
|
|
||||||
override def toJson(event: KickEvent, session: UserSession): JsObject = {
|
|
||||||
Json.obj(
|
|
||||||
"url" -> routes.MainMenuController.mainMenu().url,
|
|
||||||
"content" -> views.html.mainmenu.creategame(Some(session.user)).toString,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package util.mapper
|
|
||||||
|
|
||||||
import controllers.routes
|
|
||||||
import events.{KickEvent, LeftEvent}
|
|
||||||
import model.sessions.UserSession
|
|
||||||
import play.api.libs.json.{JsObject, Json}
|
|
||||||
|
|
||||||
object LeftEventMapper extends SimpleEventMapper[LeftEvent] {
|
|
||||||
|
|
||||||
override def id: String = "LeftEvent"
|
|
||||||
|
|
||||||
override def toJson(event: LeftEvent, session: UserSession): JsObject = {
|
|
||||||
Json.obj(
|
|
||||||
"url" -> routes.MainMenuController.mainMenu().url,
|
|
||||||
"content" -> views.html.mainmenu.creategame(Some(session.user)).toString
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package util.mapper
|
|
||||||
|
|
||||||
import events.LobbyUpdateEvent
|
|
||||||
import model.sessions.UserSession
|
|
||||||
import play.api.libs.json.{JsArray, JsObject, Json}
|
|
||||||
|
|
||||||
object LobbyUpdateEventMapper extends SimpleEventMapper[LobbyUpdateEvent] {
|
|
||||||
|
|
||||||
override def id: String = "LobbyUpdateEvent"
|
|
||||||
|
|
||||||
override def toJson(event: LobbyUpdateEvent, session: UserSession): JsObject = {
|
|
||||||
Json.obj(
|
|
||||||
"host" -> session.host,
|
|
||||||
"maxPlayers" -> session.gameLobby.maxPlayers,
|
|
||||||
"players" -> JsArray(session.gameLobby.getPlayers.values.map(player => {
|
|
||||||
Json.obj(
|
|
||||||
"id" -> player.id,
|
|
||||||
"name" -> player.name,
|
|
||||||
"self" -> (player.id == session.user.id)
|
|
||||||
)
|
|
||||||
}).toList)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package util.mapper
|
|
||||||
|
|
||||||
import controllers.routes
|
|
||||||
import de.knockoutwhist.events.global.SessionClosed
|
|
||||||
import model.sessions.UserSession
|
|
||||||
import play.api.libs.json.{JsObject, Json}
|
|
||||||
|
|
||||||
object SessionClosedMapper extends SimpleEventMapper[SessionClosed] {
|
|
||||||
|
|
||||||
override def id: String = "SessionClosed"
|
|
||||||
|
|
||||||
override def toJson(event: SessionClosed, session: UserSession): JsObject = {
|
|
||||||
Json.obj(
|
|
||||||
"url" -> routes.MainMenuController.mainMenu().url,
|
|
||||||
"content" -> views.html.mainmenu.creategame(Some(session.user)).toString,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,35 +1,6 @@
|
|||||||
@(user: Option[model.users.User], gamelobby: logic.game.GameLobby)
|
@(user: Option[model.users.User], gamelobby: logic.game.GameLobby)
|
||||||
|
|
||||||
<main class="lobby-background vh-100" id="lobbybackground">
|
<main class="lobby-background vh-100" id="lobbybackground">
|
||||||
<!-- Kick Modal -->
|
|
||||||
<div class="modal fade" data-backdrop="static" data-keyboard="false" data-focus="true" id="kickedModal" tabindex="-1" role="dialog" aria-labelledby="kickedModalTitle">
|
|
||||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="kickedModalTitle">Kicked</h5>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>You've been kicked from the lobby.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Session Closed Modal -->
|
|
||||||
<div class="modal fade" data-backdrop="static" data-keyboard="false" data-focus="true" id="sessionClosed" tabindex="-1" role="dialog" aria-labelledby="sessionClosedModalTitle">
|
|
||||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="sessionClosedModalTitle">Session Closed</h5>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>The session was closed.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Lobby -->
|
|
||||||
<div class="container d-flex flex-column" style="height: calc(100vh - 1rem);">
|
<div class="container d-flex flex-column" style="height: calc(100vh - 1rem);">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@@ -44,7 +15,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="p-3 text-center fs-4" id="playerAmount">
|
<div class="p-3 text-center fs-4" id="playerAmount">
|
||||||
Players: @gamelobby.getPlayers.size / @gamelobby.maxPlayers</div>
|
Playeramount: @gamelobby.getPlayers.size / @gamelobby.maxPlayers</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row justify-content-center align-items-center flex-grow-1">
|
<div class="row justify-content-center align-items-center flex-grow-1">
|
||||||
|
|||||||
@@ -42,9 +42,8 @@ function receiveHandEvent(eventData) {
|
|||||||
function receiveGameStateChange(eventData) {
|
function receiveGameStateChange(eventData) {
|
||||||
const content = eventData.content;
|
const content = eventData.content;
|
||||||
const title = eventData.title || 'Knockout Whist';
|
const title = eventData.title || 'Knockout Whist';
|
||||||
const url = eventData.url || null;
|
|
||||||
|
|
||||||
exchangeBody(content, title, url);
|
exchangeBody(content, title);
|
||||||
}
|
}
|
||||||
function receiveCardPlayedEvent(eventData) {
|
function receiveCardPlayedEvent(eventData) {
|
||||||
const firstCard = eventData.firstCard;
|
const firstCard = eventData.firstCard;
|
||||||
@@ -85,79 +84,6 @@ function receiveCardPlayedEvent(eventData) {
|
|||||||
`;
|
`;
|
||||||
firstCardContainer.html(newFirstCardHTML);
|
firstCardContainer.html(newFirstCardHTML);
|
||||||
}
|
}
|
||||||
function receiveLobbyUpdateEvent(eventData) {
|
|
||||||
const host = eventData.host;
|
|
||||||
const maxPlayers = eventData.maxPlayers;
|
|
||||||
const players = eventData.players;
|
|
||||||
|
|
||||||
const lobbyPlayersContainer = $('#players');
|
|
||||||
const playerAmountBox = $('#playerAmount');
|
|
||||||
|
|
||||||
let newHtml = ''
|
|
||||||
|
|
||||||
if (host) {
|
|
||||||
players.forEach(user => {
|
|
||||||
|
|
||||||
const inner = user.self ? `<h5 class="card-title">${user.name} (You)</h5>
|
|
||||||
<a href="#" class="btn btn-danger disabled" aria-disabled="true" tabindex="-1">Remove</a>`
|
|
||||||
: ` <h5 class="card-title">${user.name}</h5>
|
|
||||||
<div class="btn btn-danger" onclick="removePlayer('${user.id}')">Remove</div>`
|
|
||||||
|
|
||||||
newHtml += `<div class="col-auto my-auto m-3">
|
|
||||||
<div class="card" style="width: 18rem;">
|
|
||||||
<img src="/assets/images/profile.png" alt="Profile" class="card-img-top w-50 mx-auto mt-3" />
|
|
||||||
<div class="card-body">
|
|
||||||
${inner}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
players.forEach(user => {
|
|
||||||
|
|
||||||
const inner = user.self ? `<h5 class="card-title">${user.name} (You)</h5>` : ` <h5 class="card-title">${user.name}</h5>`
|
|
||||||
|
|
||||||
newHtml += `<div class="col-auto my-auto m-3">
|
|
||||||
<div class="card" style="width: 18rem;">
|
|
||||||
<img src="/assets/images/profile.png" alt="Profile" class="card-img-top w-50 mx-auto mt-3" />
|
|
||||||
<div class="card-body">
|
|
||||||
${inner}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
lobbyPlayersContainer.html(newHtml);
|
|
||||||
playerAmountBox.text(`Players: ${players.length} / ${maxPlayers}`);
|
|
||||||
|
|
||||||
}
|
|
||||||
function receiveKickEvent(eventData) {
|
|
||||||
$('#kickedModal').modal({
|
|
||||||
backdrop: 'static',
|
|
||||||
keyboard: false
|
|
||||||
}).modal('show');
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
receiveGameStateChange(eventData)
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
function receiveSessionClosedEvent(eventData) {
|
|
||||||
$('#sessionClosed').modal({
|
|
||||||
backdrop: 'static',
|
|
||||||
keyboard: false
|
|
||||||
}).modal('show');
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
receiveGameStateChange(eventData)
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
onEvent("ReceivedHandEvent", receiveHandEvent)
|
onEvent("ReceivedHandEvent", receiveHandEvent)
|
||||||
onEvent("GameStateChangeEvent", receiveGameStateChange)
|
onEvent("GameStateChangeEvent", receiveGameStateChange)
|
||||||
onEvent("CardPlayedEvent", receiveCardPlayedEvent)
|
onEvent("CardPlayedEvent", receiveCardPlayedEvent)
|
||||||
onEvent("LobbyUpdateEvent", receiveLobbyUpdateEvent)
|
|
||||||
onEvent("LeftEvent", receiveGameStateChange)
|
|
||||||
onEvent("KickEvent", receiveKickEvent)
|
|
||||||
onEvent("SessionClosed", receiveSessionClosedEvent)
|
|
||||||
|
|||||||
@@ -5,6 +5,3 @@ function handlePlayCard(card, dog) {
|
|||||||
function handleSkipDogLife(button) {
|
function handleSkipDogLife(button) {
|
||||||
// TODO needs implementation
|
// TODO needs implementation
|
||||||
}
|
}
|
||||||
function handleKickPlayer(playerId) {
|
|
||||||
// TODO needs implementation
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -91,7 +91,12 @@ function setupSocketHandlers(socket) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// connect/disconnect helpers
|
// connect/disconnect helpers
|
||||||
function connectWebSocket(url = "ws://localhost:9000/websocket") {
|
function connectWebSocket(url = null) {
|
||||||
|
if (!url) {
|
||||||
|
const loc = window.location;
|
||||||
|
const protocol = loc.protocol === "https:" ? "wss:" : "ws:";
|
||||||
|
url = protocol + "//" + loc.host + "/websocket";
|
||||||
|
}
|
||||||
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
|
// already connecting - return a promise that resolves on open
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=4
|
MAJOR=4
|
||||||
MINOR=3
|
MINOR=2
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
Reference in New Issue
Block a user