chg: dev: replace the legacy gos/web-socket-bundle & replace it with Mercure protocol #4
This commit is contained in:
@@ -16,9 +16,10 @@ use App\Entity\PlayedGame;
|
||||
use App\Interfaces\RpcManagerInterface;
|
||||
use DateTime;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Exception;
|
||||
use JsonException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class RpcManager
|
||||
@@ -30,13 +31,12 @@ use Psr\Log\LoggerInterface;
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 09.
|
||||
*/
|
||||
class RpcManager extends WebsocketManager implements RpcManagerInterface
|
||||
class RpcManager implements RpcManagerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
private LoggerInterface $logger,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct($logger);
|
||||
}
|
||||
|
||||
public function getConnectInformation($params): string
|
||||
@@ -45,17 +45,33 @@ class RpcManager extends WebsocketManager implements RpcManagerInterface
|
||||
$grid = $this->getGrid($gameAssoc);
|
||||
$users = null !== $grid ? $this->getUsers($gameAssoc) : null;
|
||||
|
||||
return base64_encode(json_encode([
|
||||
'grid' => $grid,
|
||||
'users' => $users,
|
||||
], JSON_THROW_ON_ERROR, 512));
|
||||
try {
|
||||
return base64_encode(json_encode([
|
||||
'grid' => $grid,
|
||||
'users' => $users,
|
||||
], JSON_THROW_ON_ERROR, 512));
|
||||
} catch (JsonException $e) {
|
||||
throw new RuntimeException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function saveGrid($data): bool
|
||||
{
|
||||
$existingGame = $this->entityManager
|
||||
->getRepository(PlayedGame::class)
|
||||
->findOneByGameAssoc($data[1]);
|
||||
|
||||
if (null !== $existingGame) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$playedGame = new PlayedGame();
|
||||
$grid = new Grid();
|
||||
$rows = json_decode(base64_decode($data[0]), true, 512, JSON_THROW_ON_ERROR);
|
||||
try {
|
||||
$rows = json_decode(base64_decode($data[0]), true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $e) {
|
||||
throw new RuntimeException($e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($rows as $row) {
|
||||
@@ -66,7 +82,6 @@ class RpcManager extends WebsocketManager implements RpcManagerInterface
|
||||
/** Save Row */
|
||||
$gridRow->setGrid($grid);
|
||||
$this->entityManager->persist($gridRow);
|
||||
|
||||
}
|
||||
|
||||
/** Save Grid */
|
||||
@@ -81,8 +96,6 @@ class RpcManager extends WebsocketManager implements RpcManagerInterface
|
||||
$this->entityManager->persist($playedGame);
|
||||
|
||||
$this->entityManager->flush();
|
||||
} catch (ORMException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
}
|
||||
|
||||
@@ -10,20 +10,20 @@
|
||||
|
||||
namespace App\Util;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Entity\Gamer;
|
||||
use App\Entity\PlayedGame;
|
||||
use App\Entity\Step;
|
||||
use App\Entity\User;
|
||||
use App\Interfaces\TopicManagerInterface;
|
||||
use DateTime;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Exception;
|
||||
use Gos\Bundle\WebSocketBundle\Client\ClientManipulatorInterface;
|
||||
use RuntimeException;
|
||||
use JsonException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Ratchet\ConnectionInterface;
|
||||
use Ratchet\Wamp\Topic;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* Class TopicManager
|
||||
@@ -35,79 +35,110 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 09.
|
||||
*/
|
||||
class TopicManager extends WebsocketManager implements TopicManagerInterface
|
||||
class TopicManager implements TopicManagerInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected ClientManipulatorInterface $clientManipulator,
|
||||
protected EntityManagerInterface $entityManager,
|
||||
protected RequestStack $requestStack,
|
||||
protected LoggerInterface $logger
|
||||
)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
private readonly HubInterface $hub,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger
|
||||
) {
|
||||
}
|
||||
|
||||
public function subscribe(Topic $topic, ConnectionInterface $connection): void
|
||||
public function subscribe(string $gameAssoc, string $userName, ?UserInterface $user): void
|
||||
{
|
||||
/** this will broadcast the message to ALL subscribers of this topic. */
|
||||
$user = $this->clientManipulator->getClient($connection);
|
||||
$userName = $user->getUserIdentifier() ?: 'anon_' . $connection->resourceId;
|
||||
$playedGame = $this->getPlayedGame($gameAssoc);
|
||||
if (null === $playedGame) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** if more user wants to connect than 2 to one channel */
|
||||
if ($topic->count() > 2) {
|
||||
$topic->remove($connection);
|
||||
} else {
|
||||
$users = $this->controlUsers($topic, $userName, $user);
|
||||
$users = $this->getUserCollection($playedGame);
|
||||
$count = $this->getPlayerCount($users);
|
||||
$isKnown = in_array($userName, array_filter(array_values($users)), true);
|
||||
|
||||
$topic->broadcast([
|
||||
'userTopicId' => $connection->resourceId,
|
||||
'channel' => $topic->getId(),
|
||||
'user' => $userName,
|
||||
'userCnt' => $topic->count(),
|
||||
'users' => $users
|
||||
]);
|
||||
/** Reject a third player who is not a reconnecting player */
|
||||
if ($count >= 2 && !$isKnown) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** Save the player to the database on a fresh join */
|
||||
if (!$isKnown && $count < 2) {
|
||||
$users = $this->saveUserToDb($gameAssoc, $userName, $user, $count + 1);
|
||||
$count = $this->getPlayerCount($users);
|
||||
}
|
||||
|
||||
$topic = 'mineseeker/channel/' . $gameAssoc;
|
||||
|
||||
try {
|
||||
$this->hub->publish(new Update(
|
||||
$topic,
|
||||
json_encode([
|
||||
'userTopicId' => $userName,
|
||||
'channel' => $topic,
|
||||
'user' => $userName,
|
||||
'userCnt' => $count,
|
||||
'users' => $users,
|
||||
], JSON_THROW_ON_ERROR)
|
||||
));
|
||||
} catch (JsonException $e) {
|
||||
throw new RuntimeException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function unSubscribe(Topic $topic, ConnectionInterface $connection): void
|
||||
public function unSubscribe(string $gameAssoc, string $userName): void
|
||||
{
|
||||
/** This will broadcasts the message to ALL subscribers of this topic. */
|
||||
$topic->broadcast(['msg' => $connection->resourceId . ' has left ' . $topic->getId()]);
|
||||
$topic = 'mineseeker/channel/' . $gameAssoc;
|
||||
|
||||
$this->hub->publish(new Update(
|
||||
$topic,
|
||||
json_encode(['msg' => $userName . ' has left ' . $topic])
|
||||
));
|
||||
}
|
||||
|
||||
public function publish(Topic $topic, ConnectionInterface $connection, $event): void
|
||||
public function publish(string $gameAssoc, string $userName, array $event): void
|
||||
{
|
||||
$user = $this->clientManipulator->getClient($connection);
|
||||
$userName = $user->getUserIdentifier();
|
||||
|
||||
/** Save every step by user to db */
|
||||
null === $event['resign']
|
||||
? $this->saveStepToDb($topic, $event)
|
||||
: $this->saveResignToDb($topic, $event['resign']);
|
||||
? $this->saveStepToDb($gameAssoc, $event)
|
||||
: $this->saveResignToDb($gameAssoc, $event['resign']);
|
||||
|
||||
$topic->broadcast([
|
||||
'userTopicId' => $connection->resourceId,
|
||||
'channel' => $topic->getId(),
|
||||
'user' => $userName,
|
||||
'userCnt' => $topic->count(),
|
||||
'data' => $event
|
||||
]);
|
||||
$playedGame = $this->getPlayedGame($gameAssoc);
|
||||
$users = $this->getUserCollection($playedGame);
|
||||
$count = $this->getPlayerCount($users);
|
||||
$topic = 'mineseeker/channel/' . $gameAssoc;
|
||||
|
||||
try {
|
||||
$this->hub->publish(new Update(
|
||||
$topic,
|
||||
json_encode([
|
||||
'userTopicId' => $userName,
|
||||
'channel' => $topic,
|
||||
'user' => $userName,
|
||||
'userCnt' => $count,
|
||||
'data' => $event,
|
||||
], JSON_THROW_ON_ERROR)
|
||||
));
|
||||
} catch (JsonException $e) {
|
||||
throw new RuntimeException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Resign event to database
|
||||
*
|
||||
* @param $topic
|
||||
* @param $color
|
||||
*/
|
||||
private function saveResignToDb(Topic $topic, $color): void
|
||||
private function getPlayedGame(string $gameAssoc): ?PlayedGame
|
||||
{
|
||||
$gameAssoc = explode('/', $topic->getId())[2];
|
||||
|
||||
/** @var PlayedGame $playedGame */
|
||||
$playedGame = $this->entityManager
|
||||
return $this->entityManager
|
||||
->getRepository(PlayedGame::class)
|
||||
->findOneByGameAssoc($gameAssoc);
|
||||
}
|
||||
|
||||
private function getPlayerCount(array $users): int
|
||||
{
|
||||
$red = '' !== $users['red'] || '' !== $users['redAnon'] ? 1 : 0;
|
||||
$blue = '' !== $users['blue'] || '' !== $users['blueAnon'] ? 1 : 0;
|
||||
|
||||
return $red + $blue;
|
||||
}
|
||||
|
||||
private function saveResignToDb(string $gameAssoc, string $color): void
|
||||
{
|
||||
$playedGame = $this->getPlayedGame($gameAssoc);
|
||||
|
||||
$playedGame->setResign($color);
|
||||
|
||||
@@ -115,30 +146,17 @@ class TopicManager extends WebsocketManager implements TopicManagerInterface
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save steps and point information to database
|
||||
*
|
||||
* @param $topic
|
||||
* @param $event
|
||||
*/
|
||||
private function saveStepToDb(Topic $topic, $event): void
|
||||
private function saveStepToDb(string $gameAssoc, array $event): void
|
||||
{
|
||||
try {
|
||||
$gameAssoc = explode('/', $topic->getId())[2];
|
||||
|
||||
/** @var PlayedGame $playedGame */
|
||||
$playedGame = $this->entityManager
|
||||
->getRepository(PlayedGame::class)
|
||||
->findOneByGameAssoc($gameAssoc);
|
||||
$playedGame = $this->getPlayedGame($gameAssoc);
|
||||
|
||||
$step = new Step();
|
||||
|
||||
$step->setRow($event['coords'][0]);
|
||||
$step->setCol($event['coords'][1]);
|
||||
$step->setWBomb($event['bomb']);
|
||||
$step->setPlayedGame($playedGame);
|
||||
$step->setCreated(new DateTime());
|
||||
|
||||
$this->entityManager->persist($step);
|
||||
|
||||
$playedGame->setBluePoints($event['bluePoints']);
|
||||
@@ -146,7 +164,6 @@ class TopicManager extends WebsocketManager implements TopicManagerInterface
|
||||
$playedGame->setBlueExplodedBomb($event['blueExplodedBomb'] ? true : null);
|
||||
$playedGame->setRedExplodedBomb($event['redExplodedBomb'] ? true : null);
|
||||
$playedGame->setUpdated(new DateTime());
|
||||
|
||||
$this->entityManager->persist($playedGame);
|
||||
|
||||
$this->entityManager->flush();
|
||||
@@ -155,62 +172,11 @@ class TopicManager extends WebsocketManager implements TopicManagerInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Control all users in a channel
|
||||
*
|
||||
* @param Topic $topic
|
||||
* @param string $userName
|
||||
* @param $user
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function controlUsers(Topic $topic, string $userName, TokenInterface $user): array
|
||||
private function saveUserToDb(string $gameAssoc, string $userName, ?UserInterface $user, int $count): array
|
||||
{
|
||||
$gameAssoc = explode('/', $topic->getId())[2];
|
||||
$playedGame = $this->getPlayedGame($gameAssoc);
|
||||
|
||||
/** @var PlayedGame $playedGame */
|
||||
$playedGame = $this->entityManager
|
||||
->getRepository(PlayedGame::class)
|
||||
->findOneByGameAssoc($gameAssoc);
|
||||
|
||||
/** @var $users {array} */
|
||||
$users = $this->getUserCollection($playedGame);
|
||||
|
||||
$red = '' !== $users['red'] || '' !== $users['redAnon'] ? 1 : 0;
|
||||
$blue = '' !== $users['blue'] || '' !== $users['blueAnon'] ? 1 : 0;
|
||||
$one = $topic->count() === 1;
|
||||
$two = $topic->count() === 2;
|
||||
|
||||
/** This checks it is a reconnection */
|
||||
if (($one && ($red + $blue === 0)) || ($two && ($red + $blue === 1))) {
|
||||
/** @var $users {array} w/ save users to database */
|
||||
$users = $this->saveUserToDb($topic, $userName, $user, $topic->count());
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save user data to database
|
||||
*
|
||||
* @param $topic
|
||||
* @param $userName
|
||||
* @param $user
|
||||
* @param $count
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function saveUserToDb(Topic $topic, string $userName, TokenInterface $user, $count)
|
||||
{
|
||||
$gameAssoc = explode('/', $topic->getId())[2];
|
||||
|
||||
/** @var PlayedGame $playedGame */
|
||||
$playedGame = $this->entityManager
|
||||
->getRepository(PlayedGame::class)
|
||||
->findOneByGameAssoc($gameAssoc);
|
||||
|
||||
/** when the user is not anonym */
|
||||
null !== $user->getUser()
|
||||
null !== $user
|
||||
? $this->saveRegisteredUser($userName, $count, $playedGame)
|
||||
: $this->saveAnonUser($userName, $count, $playedGame);
|
||||
|
||||
@@ -220,23 +186,15 @@ class TopicManager extends WebsocketManager implements TopicManagerInterface
|
||||
return $this->getUserCollection($playedGame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the registered user to the database
|
||||
*
|
||||
* @param string $userName
|
||||
* @param int $count
|
||||
* @param PlayedGame $playedGame
|
||||
*/
|
||||
private function saveRegisteredUser(string $userName, int $count, PlayedGame $playedGame)
|
||||
private function saveRegisteredUser(string $userName, int $count, PlayedGame $playedGame): void
|
||||
{
|
||||
/** @var User $FOSUser */
|
||||
/** @var User $user */
|
||||
$user = $this->entityManager
|
||||
->getRepository(User::class)
|
||||
->findOneByUsername($userName);
|
||||
|
||||
try {
|
||||
if ($count === 1) {
|
||||
/** @var $random {integer} Active player: red: 0, blue: 1 */
|
||||
$random = random_int(0, 1);
|
||||
!$random ? $playedGame->setRed($user) : $playedGame->setBlue($user);
|
||||
} else {
|
||||
@@ -249,17 +207,8 @@ class TopicManager extends WebsocketManager implements TopicManagerInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save anonymous Gamer to database
|
||||
*
|
||||
* @param string $userName
|
||||
* @param int $count
|
||||
* @param PlayedGame $playedGame
|
||||
*/
|
||||
private function saveAnonUser(string $userName, int $count, PlayedGame $playedGame)
|
||||
private function saveAnonUser(string $userName, int $count, PlayedGame $playedGame): void
|
||||
{
|
||||
// $request = $this->requestStack->getCurrentRequest(); // TODO nem megy...
|
||||
|
||||
try {
|
||||
$anon = new Gamer();
|
||||
$anon->setUsername($userName);
|
||||
@@ -267,7 +216,6 @@ class TopicManager extends WebsocketManager implements TopicManagerInterface
|
||||
$this->entityManager->persist($anon);
|
||||
|
||||
if ($count === 1) {
|
||||
/** @var $random {integer} Active player: red: 0, blue: 1 */
|
||||
$random = random_int(0, 1);
|
||||
!$random ? $playedGame->setRedAnon($anon) : $playedGame->setBlueAnon($anon);
|
||||
} else {
|
||||
@@ -280,20 +228,13 @@ class TopicManager extends WebsocketManager implements TopicManagerInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user collection from PlayedGame entity
|
||||
*
|
||||
* @param $playedGame
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getUserCollection(PlayedGame $playedGame): array
|
||||
{
|
||||
return [
|
||||
'red' => null !== $playedGame->getRed() ? $playedGame->getRed()->getUsername() : '',
|
||||
'blue' => null !== $playedGame->getBlue() ? $playedGame->getBlue()->getUsername() : '',
|
||||
'redAnon' => null !== $playedGame->getRedAnon() ? $playedGame->getRedAnon()->getUserName() : '',
|
||||
'blueAnon' => null !== $playedGame->getBlueAnon() ? $playedGame->getBlueAnon()->getUserName() : ''
|
||||
'red' => null !== $playedGame->getRed() ? $playedGame->getRed()->getUsername() : '',
|
||||
'blue' => null !== $playedGame->getBlue() ? $playedGame->getBlue()->getUsername() : '',
|
||||
'redAnon' => null !== $playedGame->getRedAnon() ? $playedGame->getRedAnon()->getUserName() : '',
|
||||
'blueAnon' => null !== $playedGame->getBlueAnon() ? $playedGame->getBlueAnon()->getUserName() : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of the SplendidBear Websites' projects.
|
||||
*
|
||||
* Copyright (c) 2019 @ www.splendidbear.org
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Util;
|
||||
|
||||
use App\Interfaces\WebsocketManagerInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class WebsocketManager
|
||||
*
|
||||
* @package App\Util
|
||||
* @author Lang <https://www.splendidbear.org>
|
||||
* @category Class
|
||||
* @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
|
||||
* @link www.splendidbear.org
|
||||
* @since 2026. 04. 09.
|
||||
*/
|
||||
class WebsocketManager implements WebsocketManagerInterface
|
||||
{
|
||||
public function __construct(private LoggerInterface $logger) { }
|
||||
|
||||
public function reConnect(EntityManagerInterface $entityManager): ?EntityManagerInterface
|
||||
{
|
||||
try {
|
||||
$connection = $entityManager->getConnection();
|
||||
|
||||
if (false === $connection->ping()) {
|
||||
$connection->close();
|
||||
$connection->connect();
|
||||
}
|
||||
} catch (RuntimeException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
}
|
||||
|
||||
return $entityManager;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user