Route every Mercure publish through an event, log pushes per-player
Every $hub->publish() call site (chat, hints, security alerts, virus alerts, game_finished, and the pre-game lobby events) now dispatches a PushMercureMessageEvent instead of publishing directly. Two listeners handle it: PublishMercureMessageListener does the actual Mercure publish exactly as before (same wire format, no client changes needed), and LogMercureMessageListener appends it to the activity log of whichever player(s) it was actually delivered to. A private /chat to one agent only gets logged for that agent, never broadcast into everyone's transcript - the event carries an explicit targetScreen (null = everyone) rather than leaving listeners to guess from the payload shape. The per-player log format moved from flat text to JSON Lines (one timestamped, structured entry per line) via a new shared SessionActivityLogger service, since a flat string can't carry an event's type or exact payload - both needed for a future feature to replay a player's full session history, not just their own commands, on page reload. The admin session log viewer now parses and formats these entries back into readable lines. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,12 +8,12 @@ use App\Game\Enum\SessionStatus;
|
||||
use App\Game\Entity\Player;
|
||||
use App\Game\Entity\Session;
|
||||
use App\Game\Entity\SessionSetting;
|
||||
use App\Game\Event\PushMercureMessageEvent;
|
||||
use App\Game\Repository\SessionSettingRepository;
|
||||
use App\Tech\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
class GameResponseService
|
||||
{
|
||||
@@ -25,8 +25,9 @@ class GameResponseService
|
||||
private Security $security,
|
||||
private PlayerService $playerService,
|
||||
private SessionSettingRepository $sessionSettingRepository,
|
||||
private HubInterface $hub,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private EventDispatcherInterface $eventDispatcher,
|
||||
private SessionActivityLogger $sessionActivityLogger,
|
||||
private string $projectDir,
|
||||
) {
|
||||
}
|
||||
@@ -72,7 +73,7 @@ class GameResponseService
|
||||
|
||||
$this->enforceLockedFilesRemovalDeadline($player->getSession());
|
||||
|
||||
$this->logSessionActivity($player, 'PLAYER: ' . $message);
|
||||
$this->sessionActivityLogger->log($player, 'player', ['content' => $message]);
|
||||
|
||||
$data = $this->handleLockedPlayer($message, $player);
|
||||
|
||||
@@ -98,28 +99,12 @@ class GameResponseService
|
||||
}
|
||||
|
||||
if ($responseLog !== '') {
|
||||
$this->logSessionActivity($player, 'SERVER: ' . trim($responseLog));
|
||||
$this->sessionActivityLogger->log($player, 'server', ['content' => trim($responseLog)]);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function logSessionActivity(Player $player, string $content): void
|
||||
{
|
||||
$sessionId = $player->getSession()->getId();
|
||||
$logDir = $this->projectDir . '/var/log/sessions/' . $sessionId;
|
||||
|
||||
if (!is_dir($logDir)) {
|
||||
mkdir($logDir, 0777, true);
|
||||
}
|
||||
|
||||
$logFile = $logDir . '/' . $player->getLogFileBasename() . '.txt';
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$logMessage = sprintf("[%s] %s\n", $timestamp, $content);
|
||||
|
||||
file_put_contents($logFile, $logMessage, FILE_APPEND);
|
||||
}
|
||||
|
||||
private function getRechten(Player $player): array
|
||||
{
|
||||
$settingName = SessionSettingType::tryFrom('RightsForPlayer' . $player->getScreen());
|
||||
@@ -430,17 +415,19 @@ class GameResponseService
|
||||
|
||||
$message = trim($message);
|
||||
|
||||
$activeGame = $player->getSession()?->getId();
|
||||
$session = $player->getSession();
|
||||
|
||||
if(is_null($activeGame))
|
||||
if(is_null($session))
|
||||
return false;
|
||||
|
||||
$topic = '/game/hub/' . $activeGame;
|
||||
try {
|
||||
$this->hub->publish(new Update($topic, json_encode([$sendTo, $message])));
|
||||
} catch (\Exception $e) {
|
||||
// Mercure might be down
|
||||
}
|
||||
// A /chat targeted at one agent (e.g. "/chat 3 ...") must only ever be
|
||||
// logged for that agent, not broadcast into everyone's transcript.
|
||||
$this->eventDispatcher->dispatch(new PushMercureMessageEvent(
|
||||
$session,
|
||||
[$sendTo, $message],
|
||||
'chat',
|
||||
(int)$sendTo === 0 ? null : (int)$sendTo,
|
||||
));
|
||||
|
||||
$this->updateChatTracking($player, (int)$sendTo);
|
||||
|
||||
@@ -480,15 +467,14 @@ class GameResponseService
|
||||
$this->entityManager->persist($setting);
|
||||
$this->entityManager->flush();
|
||||
|
||||
// Notify the player that their codes have changed
|
||||
$topic = '/game/hub/' . $session->getId();
|
||||
// Notify the player that their codes have changed - only this one (screen)
|
||||
$notification = "Security Alert: One of your verify codes was shared and has been regenerated.";
|
||||
// We send it only to this player (screen)
|
||||
try {
|
||||
$this->hub->publish(new Update($topic, json_encode([$screen, $notification])));
|
||||
} catch (\Exception $e) {
|
||||
// Mercure might be down
|
||||
}
|
||||
$this->eventDispatcher->dispatch(new PushMercureMessageEvent(
|
||||
$session,
|
||||
[$screen, $notification],
|
||||
'security_alert',
|
||||
$screen,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -963,13 +949,8 @@ class GameResponseService
|
||||
$this->entityManager->persist($everyoneVerifiedSetting);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$topic = '/game/hub/' . $session->getId();
|
||||
$message = "Mainframe Help Modus: Agents Doyle, Vega and Lennox rapports have been updated with coded messages.";
|
||||
try {
|
||||
$this->hub->publish(new Update($topic, json_encode([0, $message])));
|
||||
} catch (\Exception $e) {
|
||||
// Mercure might be down
|
||||
}
|
||||
$this->eventDispatcher->dispatch(new PushMercureMessageEvent($session, [0, $message], 'help_modus_activated'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1155,12 +1136,11 @@ class GameResponseService
|
||||
$this->entityManager->persist($session);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$topic = '/game/hub/' . $session->getId();
|
||||
try {
|
||||
$this->hub->publish(new Update($topic, json_encode(['type' => 'game_finished', 'status' => 'won'])));
|
||||
} catch (\Exception $e) {
|
||||
// Mercure might be down
|
||||
}
|
||||
$this->eventDispatcher->dispatch(new PushMercureMessageEvent(
|
||||
$session,
|
||||
['type' => 'game_finished', 'status' => 'won'],
|
||||
'game_finished',
|
||||
));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1245,13 +1225,8 @@ class GameResponseService
|
||||
}
|
||||
|
||||
if (!empty($stillLocked)) {
|
||||
$topic = '/game/hub/' . $session->getId();
|
||||
$message = 'AI VIRUS: Integrity check complete. Restored files that were not fully purged in time.';
|
||||
try {
|
||||
$this->hub->publish(new Update($topic, json_encode([0, $message, 'virus'])));
|
||||
} catch (\Exception $e) {
|
||||
// Mercure might be down
|
||||
}
|
||||
$this->eventDispatcher->dispatch(new PushMercureMessageEvent($session, [0, $message, 'virus'], 'virus_alert'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user