From 8884d15d6e2d9b91e6db5ae8d975aaa207d85d8f Mon Sep 17 00:00:00 2001 From: Lucas Morgan Date: Fri, 7 Jul 2023 16:46:26 -0500 Subject: [PATCH] Fix fire'n'forget task from blocking guest logins. --- Shogi.UI/Pages/Home/Account/AccountManager.cs | 14 ++++++-- Shogi.UI/Pages/Home/Api/ShogiApi.cs | 15 ++++---- Shogi.UI/Program.cs | 1 - Shogi.UI/Shared/ShogiSocket.cs | 36 +++++++++++-------- 4 files changed, 42 insertions(+), 24 deletions(-) diff --git a/Shogi.UI/Pages/Home/Account/AccountManager.cs b/Shogi.UI/Pages/Home/Account/AccountManager.cs index 25ccfcb..97fc001 100644 --- a/Shogi.UI/Pages/Home/Account/AccountManager.cs +++ b/Shogi.UI/Pages/Home/Account/AccountManager.cs @@ -45,8 +45,13 @@ public class AccountManager Id = response.UserId, WhichAccountPlatform = WhichAccountPlatform.Guest }); - await shogiSocket.OpenAsync(response.OneTimeToken.ToString()); await localStorage.SetAccountPlatform(WhichAccountPlatform.Guest); + // TODO: OpenAsync() sometimes doesn't return, probably because of the fire'n'forget task inside it. Figure that out. + await shogiSocket.OpenAsync(response.OneTimeToken.ToString()); + } + else + { + throw new InvalidOperationException("Failed to get token from server during guest login."); } } @@ -114,16 +119,19 @@ public class AccountManager { var platform = await localStorage.GetAccountPlatform(); await localStorage.DeleteAccountPlatform(); + await accountState.SetUser(null); if (platform == WhichAccountPlatform.Guest) { await shogiApi.GuestLogout(); - await accountState.SetUser(null); } else if (platform == WhichAccountPlatform.Microsoft) { - await accountState.SetUser(null); navigation.NavigateToLogout("authentication/logout"); } + else + { + throw new InvalidOperationException("Tried to logout without a valid account platform."); + } } } diff --git a/Shogi.UI/Pages/Home/Api/ShogiApi.cs b/Shogi.UI/Pages/Home/Api/ShogiApi.cs index 90b52ee..6607188 100644 --- a/Shogi.UI/Pages/Home/Api/ShogiApi.cs +++ b/Shogi.UI/Pages/Home/Api/ShogiApi.cs @@ -11,29 +11,32 @@ namespace Shogi.UI.Pages.Home.Api { public const string GuestClientName = "Guest"; public const string MsalClientName = "Msal"; - //public const string AnonymousClientName = "Anonymous"; private readonly JsonSerializerOptions serializerOptions; private readonly IHttpClientFactory clientFactory; private readonly AccountState accountState; + private readonly HttpClient guestHttpClient; + private readonly HttpClient msalHttpClient; public ShogiApi(IHttpClientFactory clientFactory, AccountState accountState) { serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); this.clientFactory = clientFactory; this.accountState = accountState; + this.guestHttpClient = clientFactory.CreateClient(GuestClientName); + this.msalHttpClient = clientFactory.CreateClient(MsalClientName); } private HttpClient HttpClient => accountState.User?.WhichAccountPlatform switch { - WhichAccountPlatform.Guest => clientFactory.CreateClient(GuestClientName), - WhichAccountPlatform.Microsoft => clientFactory.CreateClient(MsalClientName), + WhichAccountPlatform.Guest => this.guestHttpClient, + WhichAccountPlatform.Microsoft => this.msalHttpClient, _ => throw new InvalidOperationException("AccountState.User must not be null during API call.") }; public async Task GuestLogout() { - var response = await HttpClient.PutAsync(new Uri("User/GuestLogout", UriKind.Relative), null); + var response = await this.guestHttpClient.PutAsync(new Uri("User/GuestLogout", UriKind.Relative), null); response.EnsureSuccessStatusCode(); } @@ -63,8 +66,8 @@ namespace Shogi.UI.Pages.Home.Api public async Task GetToken(WhichAccountPlatform whichAccountPlatform) { var httpClient = whichAccountPlatform == WhichAccountPlatform.Microsoft - ? clientFactory.CreateClient(MsalClientName) - : clientFactory.CreateClient(GuestClientName); + ? this.msalHttpClient + : this.guestHttpClient; var response = await httpClient.GetAsync(RelativeUri("User/Token")); if (response.IsSuccessStatusCode) { diff --git a/Shogi.UI/Program.cs b/Shogi.UI/Program.cs index 23656de..c52fcae 100644 --- a/Shogi.UI/Program.cs +++ b/Shogi.UI/Program.cs @@ -51,7 +51,6 @@ static void ConfigureDependencies(IServiceCollection services, IConfiguration co }); // https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/dependency-injection?view=aspnetcore-6.0#service-lifetime - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/Shogi.UI/Shared/ShogiSocket.cs b/Shogi.UI/Shared/ShogiSocket.cs index 0bc6459..27bd0ea 100644 --- a/Shogi.UI/Shared/ShogiSocket.cs +++ b/Shogi.UI/Shared/ShogiSocket.cs @@ -14,16 +14,16 @@ public class ShogiSocket : IDisposable public event AsyncEventHandler? OnSessionJoined; public event AsyncEventHandler? OnPlayerMoved; - private readonly ClientWebSocket socket; + private ClientWebSocket socket; private readonly JsonSerializerOptions serializerOptions; private readonly UriBuilder uriBuilder; private readonly CancellationTokenSource cancelToken; private readonly IMemoryOwner memoryOwner; private bool disposedValue; - public ShogiSocket(IConfiguration configuration, ClientWebSocket socket, JsonSerializerOptions serializerOptions) + public ShogiSocket(IConfiguration configuration, JsonSerializerOptions serializerOptions) { - this.socket = socket; + this.socket = new ClientWebSocket(); this.serializerOptions = serializerOptions; this.uriBuilder = new UriBuilder(configuration["SocketUrl"] ?? throw new InvalidOperationException("SocketUrl configuration is missing.")); this.cancelToken = new CancellationTokenSource(); @@ -36,22 +36,30 @@ public class ShogiSocket : IDisposable { await this.socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing before opening a new connection.", CancellationToken.None); } + if (this.socket.State == WebSocketState.Closed) + { + this.socket.Dispose(); + this.socket = new ClientWebSocket(); // Because you can't reopen a closed socket. + } + else + { + Console.WriteLine("Opening socket and existing socket state is " + this.socket.State.ToString()); + } uriBuilder.Query = new QueryBuilder { { "token", token } }.ToQueryString().Value; - - Console.WriteLine("ShogiSocket.OpenAsync socket state is {0}", this.socket.State.ToString()); - await socket.ConnectAsync(this.uriBuilder.Uri, cancelToken.Token); // Fire and forget! I'm way too lazy to write my own javascript interop to a web worker. Nooo thanks. - _ = Listen().ContinueWith(async antecedent => - { - this.cancelToken.Cancel(); - await this.socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Page was probably closed or refresh.", CancellationToken.None); - if (antecedent.Exception != null) + _ = Listen() + .ContinueWith(async antecedent => { - throw antecedent.Exception; - } - }, TaskContinuationOptions.OnlyOnFaulted); + this.cancelToken.Cancel(); + await this.socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Page was probably closed or refresh.", CancellationToken.None); + if (antecedent.Exception != null) + { + throw antecedent.Exception; + } + }, TaskContinuationOptions.OnlyOnFaulted) + .ConfigureAwait(false); } private async Task Listen()