From 8a25c0ed3520a26227363778fe5ad045cd7bbca4 Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Sat, 28 Jan 2023 13:21:47 -0600 Subject: [PATCH] yep --- Shogi.Api/Controllers/SessionsController.cs | 8 + Shogi.Api/Controllers/UserController.cs | 108 ++++++------ Shogi.Api/Services/SocketService.cs | 161 +++++++++--------- .../Socket/PlayerHasMovedMessage.cs | 22 +-- .../Socket/SessionCreatedSocketMessage.cs | 7 +- .../SessionJoinedByPlayerSocketMessage.cs | 9 + Shogi.UI/App.razor | 16 -- Shogi.UI/Pages/Home/Account/AccountManager.cs | 35 ++-- Shogi.UI/Pages/Home/Account/AccountState.cs | 35 ++-- Shogi.UI/Pages/Home/Account/User.cs | 3 +- Shogi.UI/Pages/Home/Api/IShogiApi.cs | 2 +- Shogi.UI/Pages/Home/Api/ShogiApi.cs | 17 +- .../Pages/Home/GameBoard/EmptyGameBoard.razor | 5 - Shogi.UI/Pages/Home/GameBoard/GameBoard.razor | 26 ++- .../GameBoard/GameBoardPresentation.razor | 67 +++++--- .../Home/GameBoard/SeatedGameBoard.razor | 28 +-- .../Home/GameBoard/SpectatorGameBoard.razor | 22 ++- Shogi.UI/Pages/Home/GameBrowser.razor | 36 ++-- Shogi.UI/Pages/Home/Home.razor | 16 +- Shogi.UI/Pages/Home/PageHeader.razor | 11 +- Shogi.UI/Program.cs | 84 ++++----- Shogi.UI/Shared/Events.cs | 8 + Shogi.UI/Shared/LoginDisplay.razor | 24 --- Shogi.UI/Shared/MyNotAuthorized.razor | 5 - Shogi.UI/Shared/RedirectToLogin.razor | 14 -- Shogi.UI/Shared/ShogiSocket.cs | 33 +++- 26 files changed, 443 insertions(+), 359 deletions(-) create mode 100644 Shogi.Contracts/Socket/SessionJoinedByPlayerSocketMessage.cs create mode 100644 Shogi.UI/Shared/Events.cs delete mode 100644 Shogi.UI/Shared/LoginDisplay.razor delete mode 100644 Shogi.UI/Shared/MyNotAuthorized.razor delete mode 100644 Shogi.UI/Shared/RedirectToLogin.razor diff --git a/Shogi.Api/Controllers/SessionsController.cs b/Shogi.Api/Controllers/SessionsController.cs index ac6b453..75614aa 100644 --- a/Shogi.Api/Controllers/SessionsController.cs +++ b/Shogi.Api/Controllers/SessionsController.cs @@ -77,6 +77,11 @@ public class SessionsController : ControllerBase return Ok(await this.queryRespository.ReadSessionPlayerCount(this.User.GetShogiUserId())); } + /// + /// Fetch the session and latest board state. Also subscribe the user to socket events for this session. + /// + /// + /// [HttpGet("{name}")] public async Task> GetSession(string name) { @@ -113,6 +118,7 @@ public class SessionsController : ControllerBase session.AddPlayer2(User.GetShogiUserId()); await sessionRepository.SetPlayer2(name, User.GetShogiUserId()); + await communicationManager.BroadcastToAll(new SessionJoinedByPlayerSocketMessage()); return this.Ok(); } return this.Conflict("This game already has two players."); @@ -144,6 +150,8 @@ public class SessionsController : ControllerBase return this.Conflict(e.Message); } await sessionRepository.CreateMove(sessionName, command); + + // Send socket message to both players so their clients know that new board state is available. await communicationManager.BroadcastToPlayers( new PlayerHasMovedMessage { diff --git a/Shogi.Api/Controllers/UserController.cs b/Shogi.Api/Controllers/UserController.cs index a552b4b..c147960 100644 --- a/Shogi.Api/Controllers/UserController.cs +++ b/Shogi.Api/Controllers/UserController.cs @@ -14,60 +14,68 @@ namespace Shogi.Api.Controllers; [Authorize] public class UserController : ControllerBase { - private readonly ISocketTokenCache tokenCache; - private readonly ISocketConnectionManager connectionManager; - private readonly IUserRepository userRepository; - private readonly IShogiUserClaimsTransformer claimsTransformation; - private readonly AuthenticationProperties authenticationProps; + private readonly ISocketTokenCache tokenCache; + private readonly ISocketConnectionManager connectionManager; + private readonly IUserRepository userRepository; + private readonly IShogiUserClaimsTransformer claimsTransformation; + private readonly AuthenticationProperties authenticationProps; - public UserController( - ILogger logger, - ISocketTokenCache tokenCache, - ISocketConnectionManager connectionManager, - IUserRepository userRepository, - IShogiUserClaimsTransformer claimsTransformation) - { - this.tokenCache = tokenCache; - this.connectionManager = connectionManager; - this.userRepository = userRepository; - this.claimsTransformation = claimsTransformation; - authenticationProps = new AuthenticationProperties - { - AllowRefresh = true, - IsPersistent = true - }; - } + public UserController( + ILogger logger, + ISocketTokenCache tokenCache, + ISocketConnectionManager connectionManager, + IUserRepository userRepository, + IShogiUserClaimsTransformer claimsTransformation) + { + this.tokenCache = tokenCache; + this.connectionManager = connectionManager; + this.userRepository = userRepository; + this.claimsTransformation = claimsTransformation; + authenticationProps = new AuthenticationProperties + { + AllowRefresh = true, + IsPersistent = true + }; + } - [HttpGet("Token")] - public ActionResult GetWebSocketToken() - { - var userId = User.GetShogiUserId(); - var displayName = User.GetShogiUserDisplayname(); + [HttpGet("Token")] + public ActionResult GetWebSocketToken() + { + var userId = User.GetShogiUserId(); + var displayName = User.GetShogiUserDisplayname(); - var token = tokenCache.GenerateToken(userId); - return new CreateTokenResponse - { - DisplayName = displayName, - OneTimeToken = token, - UserId = userId - }; - } + var token = tokenCache.GenerateToken(userId); + return new CreateTokenResponse + { + DisplayName = displayName, + OneTimeToken = token, + UserId = userId + }; + } - [AllowAnonymous] - [HttpGet("LoginAsGuest")] - public async Task GuestLogin() - { - var principal = await this.claimsTransformation.CreateClaimsFromGuestPrincipal(User); - if (principal != null) - { - await HttpContext.SignInAsync( - CookieAuthenticationDefaults.AuthenticationScheme, - principal, - authenticationProps - ); - } - return Ok(); - } + /// + /// + /// Used by cookie authentication. + /// + [AllowAnonymous] + [HttpGet("LoginAsGuest")] + public async Task GuestLogin([FromQuery] string returnUrl) + { + var principal = await this.claimsTransformation.CreateClaimsFromGuestPrincipal(User); + if (principal != null) + { + await HttpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + principal, + authenticationProps + ); + } + if (!string.IsNullOrWhiteSpace(returnUrl)) + { + return Redirect(returnUrl); + } + return Ok(); + } [HttpPut("GuestLogout")] public async Task GuestLogout() diff --git a/Shogi.Api/Services/SocketService.cs b/Shogi.Api/Services/SocketService.cs index 8c7666a..c3bf257 100644 --- a/Shogi.Api/Services/SocketService.cs +++ b/Shogi.Api/Services/SocketService.cs @@ -9,91 +9,96 @@ using System.Text.Json; namespace Shogi.Api.Services { - public interface ISocketService - { - Task HandleSocketRequest(HttpContext context); - } - - /// - /// Services a single websocket connection. Authenticates the socket connection, accepts messages, and sends messages. - /// - public class SocketService : ISocketService - { - private readonly ILogger logger; - private readonly ISocketConnectionManager communicationManager; - private readonly ISocketTokenCache tokenManager; - - public SocketService( - ILogger logger, - ISocketConnectionManager communicationManager, - ISocketTokenCache tokenManager) : base() + public interface ISocketService { - this.logger = logger; - this.communicationManager = communicationManager; - this.tokenManager = tokenManager; + Task HandleSocketRequest(HttpContext context); } - public async Task HandleSocketRequest(HttpContext context) + /// + /// Services a single websocket connection. Authenticates the socket connection, accepts messages, and sends messages. + /// + public class SocketService : ISocketService { - if (!context.Request.Query.Keys.Contains("token")) - { - context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; - return; - } - var token = Guid.Parse(context.Request.Query["token"][0]); - var userName = tokenManager.GetUsername(token); + private readonly ILogger logger; + private readonly ISocketConnectionManager communicationManager; + private readonly ISocketTokenCache tokenManager; - if (string.IsNullOrEmpty(userName)) - { - context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; - return; - } - var socket = await context.WebSockets.AcceptWebSocketAsync(); + public SocketService( + ILogger logger, + ISocketConnectionManager communicationManager, + ISocketTokenCache tokenManager) : base() + { + this.logger = logger; + this.communicationManager = communicationManager; + this.tokenManager = tokenManager; + } - communicationManager.Subscribe(socket, userName); - while (socket.State == WebSocketState.Open) - { - try + public async Task HandleSocketRequest(HttpContext context) { - var message = await socket.ReceiveTextAsync(); - if (string.IsNullOrWhiteSpace(message)) continue; - logger.LogInformation("Request \n{0}\n", message); - var request = JsonSerializer.Deserialize(message); - if (request == null || !Enum.IsDefined(typeof(SocketAction), request.Action)) - { - await socket.SendTextAsync("Error: Action not recognized."); - continue; - } - switch (request.Action) - { - default: - await socket.SendTextAsync($"Received your message with action {request.Action}, but did no work."); - break; - } + if (!context.Request.Query.Keys.Contains("token")) + { + context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + return; + } + var token = Guid.Parse(context.Request.Query["token"][0] ?? throw new InvalidOperationException("Token expected during socket connection request, but was not sent.")); + var userName = tokenManager.GetUsername(token); + + if (string.IsNullOrEmpty(userName)) + { + context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + return; + } + var socket = await context.WebSockets.AcceptWebSocketAsync(); + + communicationManager.Subscribe(socket, userName); + // TODO: I probably don't need this while-loop anymore? Perhaps unsubscribe when a disconnect is detected instead. + while (socket.State.HasFlag(WebSocketState.Open)) + { + try + { + var message = await socket.ReceiveTextAsync(); + if (string.IsNullOrWhiteSpace(message)) continue; + logger.LogInformation("Request \n{0}\n", message); + var request = JsonSerializer.Deserialize(message); + if (request == null || !Enum.IsDefined(typeof(SocketAction), request.Action)) + { + await socket.SendTextAsync("Error: Action not recognized."); + continue; + } + switch (request.Action) + { + default: + await socket.SendTextAsync($"Received your message with action {request.Action}, but did no work."); + break; + } + } + catch (OperationCanceledException ex) + { + logger.LogError(ex.Message); + } + catch (WebSocketException ex) + { + logger.LogInformation($"{nameof(WebSocketException)} in {nameof(SocketConnectionManager)}."); + logger.LogInformation("Probably tried writing to a closed socket."); + logger.LogError(ex.Message); + } + communicationManager.Unsubscribe(userName); + + if (!socket.State.HasFlag(WebSocketState.Closed) && !socket.State.HasFlag(WebSocketState.Aborted)) + { + try + { + await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, + "Socket closed", + CancellationToken.None); + } + catch (Exception ex) + { + Console.WriteLine($"Ignored exception during socket closing. {ex.Message}"); + } + } + + } } - catch (OperationCanceledException ex) - { - logger.LogError(ex.Message); - } - catch (WebSocketException ex) - { - logger.LogInformation($"{nameof(WebSocketException)} in {nameof(SocketConnectionManager)}."); - logger.LogInformation("Probably tried writing to a closed socket."); - logger.LogError(ex.Message); - } - communicationManager.Unsubscribe(userName); - } } - - public async Task ValidateRequestAndReplyIfInvalid(WebSocket socket, IValidator validator, TRequest request) - { - var results = validator.Validate(request); - if (!results.IsValid) - { - var errors = string.Join('\n', results.Errors.Select(_ => _.ErrorMessage)); - await socket.SendTextAsync(errors); - } - return results.IsValid; - } - } } diff --git a/Shogi.Contracts/Socket/PlayerHasMovedMessage.cs b/Shogi.Contracts/Socket/PlayerHasMovedMessage.cs index c55ccdd..4309246 100644 --- a/Shogi.Contracts/Socket/PlayerHasMovedMessage.cs +++ b/Shogi.Contracts/Socket/PlayerHasMovedMessage.cs @@ -4,15 +4,17 @@ namespace Shogi.Contracts.Socket; public class PlayerHasMovedMessage : ISocketResponse { - public SocketAction Action { get; } - public string SessionName { get; set; } - /// - /// The player that made the move. - /// - public string PlayerName { get; set; } + public SocketAction Action { get; } + public string SessionName { get; set; } + /// + /// The player that made the move. + /// + public string PlayerName { get; set; } - public PlayerHasMovedMessage() - { - Action = SocketAction.PieceMoved; - } + public PlayerHasMovedMessage() + { + Action = SocketAction.PieceMoved; + SessionName = string.Empty; + PlayerName = string.Empty; + } } diff --git a/Shogi.Contracts/Socket/SessionCreatedSocketMessage.cs b/Shogi.Contracts/Socket/SessionCreatedSocketMessage.cs index e447852..0dd8b3f 100644 --- a/Shogi.Contracts/Socket/SessionCreatedSocketMessage.cs +++ b/Shogi.Contracts/Socket/SessionCreatedSocketMessage.cs @@ -4,10 +4,5 @@ namespace Shogi.Contracts.Socket; public class SessionCreatedSocketMessage : ISocketResponse { - public SocketAction Action { get; } - - public SessionCreatedSocketMessage() - { - Action = SocketAction.SessionCreated; - } + public SocketAction Action => SocketAction.SessionCreated; } diff --git a/Shogi.Contracts/Socket/SessionJoinedByPlayerSocketMessage.cs b/Shogi.Contracts/Socket/SessionJoinedByPlayerSocketMessage.cs new file mode 100644 index 0000000..70fc4bf --- /dev/null +++ b/Shogi.Contracts/Socket/SessionJoinedByPlayerSocketMessage.cs @@ -0,0 +1,9 @@ +using Shogi.Contracts.Types; + +namespace Shogi.Contracts.Socket +{ + public class SessionJoinedByPlayerSocketMessage : ISocketResponse + { + public SocketAction Action => SocketAction.SessionJoined; + } +} diff --git a/Shogi.UI/App.razor b/Shogi.UI/App.razor index 38fe056..efa8cea 100644 --- a/Shogi.UI/App.razor +++ b/Shogi.UI/App.razor @@ -3,22 +3,6 @@ - @* - - Authorizing!! - - - @if (context.User.Identity?.IsAuthenticated != true) - { - - } - else - { -

You are not authorized to access this resource.

- } -
-
*@ -
Not found diff --git a/Shogi.UI/Pages/Home/Account/AccountManager.cs b/Shogi.UI/Pages/Home/Account/AccountManager.cs index c94083d..ec98ea3 100644 --- a/Shogi.UI/Pages/Home/Account/AccountManager.cs +++ b/Shogi.UI/Pages/Home/Account/AccountManager.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Shogi.UI.Pages.Home.Api; using Shogi.UI.Shared; -using System.Text.Json; namespace Shogi.UI.Pages.Home.Account; @@ -35,21 +34,23 @@ public class AccountManager this.shogiSocket = shogiSocket; } - private User? MyUser { get => accountState.User; set => accountState.User = value; } + private User? MyUser => accountState.User; + + private Task SetUser(User user) => accountState.SetUser(user); + public async Task LoginWithGuestAccount() { var response = await shogiApi.GetToken(WhichAccountPlatform.Guest); if (response != null) { - MyUser = new User + await SetUser(new User { DisplayName = response.DisplayName, Id = response.UserId, - OneTimeSocketToken = response.OneTimeToken, WhichAccountPlatform = WhichAccountPlatform.Guest - }; - await shogiSocket.OpenAsync(MyUser.Value.OneTimeSocketToken.ToString()); + }); + await shogiSocket.OpenAsync(response.OneTimeToken.ToString()); await localStorage.SetAccountPlatform(WhichAccountPlatform.Guest); } } @@ -80,11 +81,12 @@ public class AccountManager var response = await shogiApi.GetToken(WhichAccountPlatform.Guest); if (response != null) { - MyUser = new User( + await accountState.SetUser(new User( Id: response.UserId, DisplayName: response.DisplayName, - WhichAccountPlatform: WhichAccountPlatform.Guest, - OneTimeSocketToken: response.OneTimeToken); + WhichAccountPlatform: WhichAccountPlatform.Guest)); + await shogiSocket.OpenAsync(response.OneTimeToken.ToString()); + return true; } } else if (platform == WhichAccountPlatform.Microsoft) @@ -101,26 +103,21 @@ public class AccountManager } var id = state.User.Claims.Single(claim => claim.Type == "oid").Value; var displayName = state.User.Identity.Name; - MyUser = new User( + await accountState.SetUser(new User( Id: id, DisplayName: displayName, - WhichAccountPlatform: WhichAccountPlatform.Microsoft, - OneTimeSocketToken: response.OneTimeToken); + WhichAccountPlatform: WhichAccountPlatform.Microsoft)); + await shogiSocket.OpenAsync(response.OneTimeToken.ToString()); + return true; } } - if (MyUser != null) - { - await shogiSocket.OpenAsync(MyUser.Value.OneTimeSocketToken.ToString()); - return true; - } - return false; } public async Task LogoutAsync() { - MyUser = null; + await accountState.SetUser(null); var platform = await localStorage.GetAccountPlatform(); await localStorage.DeleteAccountPlatform(); diff --git a/Shogi.UI/Pages/Home/Account/AccountState.cs b/Shogi.UI/Pages/Home/Account/AccountState.cs index c5b7e85..9b8f43f 100644 --- a/Shogi.UI/Pages/Home/Account/AccountState.cs +++ b/Shogi.UI/Pages/Home/Account/AccountState.cs @@ -1,28 +1,27 @@ -namespace Shogi.UI.Pages.Home.Account; +using static Shogi.UI.Shared.Events; + +namespace Shogi.UI.Pages.Home.Account; public class AccountState { - public event EventHandler? LoginChangedEvent; + public event AsyncEventHandler? LoginChangedEvent; - private User? user; - public User? User + public User? User { get; private set; } + + public Task SetUser(User? user) { - get => user; - set + User = user; + return EmitLoginChangedEvent(); + } + + private async Task EmitLoginChangedEvent() + { + if (LoginChangedEvent is not null) { - if (user != value) + await LoginChangedEvent.Invoke(new LoginEventArgs { - user = value; - EmitLoginChangedEvent(); - } + User = User + }); } } - - private void EmitLoginChangedEvent() - { - LoginChangedEvent?.Invoke(this, new LoginEventArgs - { - User = User - }); - } } diff --git a/Shogi.UI/Pages/Home/Account/User.cs b/Shogi.UI/Pages/Home/Account/User.cs index caada77..308d425 100644 --- a/Shogi.UI/Pages/Home/Account/User.cs +++ b/Shogi.UI/Pages/Home/Account/User.cs @@ -3,8 +3,7 @@ public readonly record struct User( string Id, string DisplayName, - WhichAccountPlatform WhichAccountPlatform, - Guid OneTimeSocketToken) + WhichAccountPlatform WhichAccountPlatform) { } } diff --git a/Shogi.UI/Pages/Home/Api/IShogiApi.cs b/Shogi.UI/Pages/Home/Api/IShogiApi.cs index 1510035..8f5d384 100644 --- a/Shogi.UI/Pages/Home/Api/IShogiApi.cs +++ b/Shogi.UI/Pages/Home/Api/IShogiApi.cs @@ -12,6 +12,6 @@ public interface IShogiApi Task GetToken(WhichAccountPlatform whichAccountPlatform); Task GuestLogout(); Task Move(string sessionName, MovePieceCommand move); - Task PatchJoinGame(string name); + Task PatchJoinGame(string name); Task PostSession(string name, bool isPrivate); } \ No newline at end of file diff --git a/Shogi.UI/Pages/Home/Api/ShogiApi.cs b/Shogi.UI/Pages/Home/Api/ShogiApi.cs index a9562a2..90b52ee 100644 --- a/Shogi.UI/Pages/Home/Api/ShogiApi.cs +++ b/Shogi.UI/Pages/Home/Api/ShogiApi.cs @@ -65,8 +65,16 @@ namespace Shogi.UI.Pages.Home.Api var httpClient = whichAccountPlatform == WhichAccountPlatform.Microsoft ? clientFactory.CreateClient(MsalClientName) : clientFactory.CreateClient(GuestClientName); - var response = await httpClient.GetFromJsonAsync(RelativeUri("User/Token"), serializerOptions); - return response; + var response = await httpClient.GetAsync(RelativeUri("User/Token")); + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(); + if (!string.IsNullOrEmpty(content)) + { + return await response.Content.ReadFromJsonAsync(serializerOptions); + } + } + return null; } public async Task Move(string sessionName, MovePieceCommand command) @@ -83,10 +91,9 @@ namespace Shogi.UI.Pages.Home.Api return response.StatusCode; } - public async Task PatchJoinGame(string name) + public Task PatchJoinGame(string name) { - var response = await HttpClient.PatchAsync(RelativeUri($"Sessions/{name}/Join"), null); - return response.StatusCode; + return HttpClient.PatchAsync(RelativeUri($"Sessions/{name}/Join"), null); } private static Uri RelativeUri(string path) => new Uri(path, UriKind.Relative); diff --git a/Shogi.UI/Pages/Home/GameBoard/EmptyGameBoard.razor b/Shogi.UI/Pages/Home/GameBoard/EmptyGameBoard.razor index b18b89f..0473868 100644 --- a/Shogi.UI/Pages/Home/GameBoard/EmptyGameBoard.razor +++ b/Shogi.UI/Pages/Home/GameBoard/EmptyGameBoard.razor @@ -3,9 +3,4 @@ @code { - protected override void OnInitialized() - { - base.OnInitialized(); - Console.WriteLine("Empty Game Board."); - } } diff --git a/Shogi.UI/Pages/Home/GameBoard/GameBoard.razor b/Shogi.UI/Pages/Home/GameBoard/GameBoard.razor index c74d924..def4991 100644 --- a/Shogi.UI/Pages/Home/GameBoard/GameBoard.razor +++ b/Shogi.UI/Pages/Home/GameBoard/GameBoard.razor @@ -1,9 +1,11 @@ @using Shogi.Contracts.Api +@using Shogi.Contracts.Socket; @using Shogi.Contracts.Types; @using System.Text.RegularExpressions; @inject IShogiApi ShogiApi @inject AccountState Account; @inject PromotePrompt PromotePrompt; +@inject ShogiSocket ShogiSocket; @if (session == null) { @@ -11,11 +13,11 @@ } else if (isSpectating) { - + } else { - + } @@ -27,6 +29,13 @@ else private WhichPlayer perspective; private bool isSpectating; + protected override void OnInitialized() + { + base.OnInitialized(); + ShogiSocket.OnPlayerMoved += OnPlayerMoved_RefetchSession; + ShogiSocket.OnSessionJoined += + } + protected override async Task OnParametersSetAsync() { await RefetchSession(); @@ -34,7 +43,7 @@ else async Task RefetchSession() { - if (!string.IsNullOrWhiteSpace(SessionName)) + if (!string.IsNullOrWhiteSpace(SessionName)) { this.session = await ShogiApi.GetSession(SessionName); if (this.session != null) @@ -44,9 +53,18 @@ else this.perspective = accountId == session.Player2 ? WhichPlayer.Player2 : WhichPlayer.Player1; this.isSpectating = !(accountId == this.session.Player1 || accountId == this.session.Player2); Console.WriteLine($"IsSpectating - {isSpectating}. AccountId - {accountId}. Player1 - {this.session.Player1}. Player2 - {this.session.Player2}"); + } + StateHasChanged(); } } - + Task OnPlayerMoved_RefetchSession(PlayerHasMovedMessage args) + { + if (args.SessionName == SessionName) + { + return RefetchSession(); + } + return Task.CompletedTask; + } } diff --git a/Shogi.UI/Pages/Home/GameBoard/GameBoardPresentation.razor b/Shogi.UI/Pages/Home/GameBoard/GameBoardPresentation.razor index c961965..6759e45 100644 --- a/Shogi.UI/Pages/Home/GameBoard/GameBoardPresentation.razor +++ b/Shogi.UI/Pages/Home/GameBoard/GameBoardPresentation.razor @@ -68,10 +68,10 @@