Fix fire'n'forget task from blocking guest logins.

This commit is contained in:
Lucas Morgan
2023-07-07 16:46:26 -05:00
parent dacd0475f9
commit 8884d15d6e
4 changed files with 42 additions and 24 deletions

View File

@@ -45,8 +45,13 @@ public class AccountManager
Id = response.UserId, Id = response.UserId,
WhichAccountPlatform = WhichAccountPlatform.Guest WhichAccountPlatform = WhichAccountPlatform.Guest
}); });
await shogiSocket.OpenAsync(response.OneTimeToken.ToString());
await localStorage.SetAccountPlatform(WhichAccountPlatform.Guest); 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(); var platform = await localStorage.GetAccountPlatform();
await localStorage.DeleteAccountPlatform(); await localStorage.DeleteAccountPlatform();
await accountState.SetUser(null);
if (platform == WhichAccountPlatform.Guest) if (platform == WhichAccountPlatform.Guest)
{ {
await shogiApi.GuestLogout(); await shogiApi.GuestLogout();
await accountState.SetUser(null);
} }
else if (platform == WhichAccountPlatform.Microsoft) else if (platform == WhichAccountPlatform.Microsoft)
{ {
await accountState.SetUser(null);
navigation.NavigateToLogout("authentication/logout"); navigation.NavigateToLogout("authentication/logout");
} }
else
{
throw new InvalidOperationException("Tried to logout without a valid account platform.");
}
} }
} }

View File

@@ -11,29 +11,32 @@ namespace Shogi.UI.Pages.Home.Api
{ {
public const string GuestClientName = "Guest"; public const string GuestClientName = "Guest";
public const string MsalClientName = "Msal"; public const string MsalClientName = "Msal";
//public const string AnonymousClientName = "Anonymous";
private readonly JsonSerializerOptions serializerOptions; private readonly JsonSerializerOptions serializerOptions;
private readonly IHttpClientFactory clientFactory; private readonly IHttpClientFactory clientFactory;
private readonly AccountState accountState; private readonly AccountState accountState;
private readonly HttpClient guestHttpClient;
private readonly HttpClient msalHttpClient;
public ShogiApi(IHttpClientFactory clientFactory, AccountState accountState) public ShogiApi(IHttpClientFactory clientFactory, AccountState accountState)
{ {
serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);
this.clientFactory = clientFactory; this.clientFactory = clientFactory;
this.accountState = accountState; this.accountState = accountState;
this.guestHttpClient = clientFactory.CreateClient(GuestClientName);
this.msalHttpClient = clientFactory.CreateClient(MsalClientName);
} }
private HttpClient HttpClient => accountState.User?.WhichAccountPlatform switch private HttpClient HttpClient => accountState.User?.WhichAccountPlatform switch
{ {
WhichAccountPlatform.Guest => clientFactory.CreateClient(GuestClientName), WhichAccountPlatform.Guest => this.guestHttpClient,
WhichAccountPlatform.Microsoft => clientFactory.CreateClient(MsalClientName), WhichAccountPlatform.Microsoft => this.msalHttpClient,
_ => throw new InvalidOperationException("AccountState.User must not be null during API call.") _ => throw new InvalidOperationException("AccountState.User must not be null during API call.")
}; };
public async Task GuestLogout() 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(); response.EnsureSuccessStatusCode();
} }
@@ -63,8 +66,8 @@ namespace Shogi.UI.Pages.Home.Api
public async Task<CreateTokenResponse?> GetToken(WhichAccountPlatform whichAccountPlatform) public async Task<CreateTokenResponse?> GetToken(WhichAccountPlatform whichAccountPlatform)
{ {
var httpClient = whichAccountPlatform == WhichAccountPlatform.Microsoft var httpClient = whichAccountPlatform == WhichAccountPlatform.Microsoft
? clientFactory.CreateClient(MsalClientName) ? this.msalHttpClient
: clientFactory.CreateClient(GuestClientName); : this.guestHttpClient;
var response = await httpClient.GetAsync(RelativeUri("User/Token")); var response = await httpClient.GetAsync(RelativeUri("User/Token"));
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {

View File

@@ -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 // https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/dependency-injection?view=aspnetcore-6.0#service-lifetime
services.AddScoped<ClientWebSocket>();
services.AddScoped<AccountManager>(); services.AddScoped<AccountManager>();
services.AddScoped<AccountState>(); services.AddScoped<AccountState>();
services.AddScoped<ShogiSocket>(); services.AddScoped<ShogiSocket>();

View File

@@ -14,16 +14,16 @@ public class ShogiSocket : IDisposable
public event AsyncEventHandler<SessionJoinedByPlayerSocketMessage>? OnSessionJoined; public event AsyncEventHandler<SessionJoinedByPlayerSocketMessage>? OnSessionJoined;
public event AsyncEventHandler<PlayerHasMovedMessage>? OnPlayerMoved; public event AsyncEventHandler<PlayerHasMovedMessage>? OnPlayerMoved;
private readonly ClientWebSocket socket; private ClientWebSocket socket;
private readonly JsonSerializerOptions serializerOptions; private readonly JsonSerializerOptions serializerOptions;
private readonly UriBuilder uriBuilder; private readonly UriBuilder uriBuilder;
private readonly CancellationTokenSource cancelToken; private readonly CancellationTokenSource cancelToken;
private readonly IMemoryOwner<byte> memoryOwner; private readonly IMemoryOwner<byte> memoryOwner;
private bool disposedValue; 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.serializerOptions = serializerOptions;
this.uriBuilder = new UriBuilder(configuration["SocketUrl"] ?? throw new InvalidOperationException("SocketUrl configuration is missing.")); this.uriBuilder = new UriBuilder(configuration["SocketUrl"] ?? throw new InvalidOperationException("SocketUrl configuration is missing."));
this.cancelToken = new CancellationTokenSource(); 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); 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; 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); 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. // Fire and forget! I'm way too lazy to write my own javascript interop to a web worker. Nooo thanks.
_ = Listen().ContinueWith(async antecedent => _ = 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)
{ {
throw antecedent.Exception; this.cancelToken.Cancel();
} await this.socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Page was probably closed or refresh.", CancellationToken.None);
}, TaskContinuationOptions.OnlyOnFaulted); if (antecedent.Exception != null)
{
throw antecedent.Exception;
}
}, TaskContinuationOptions.OnlyOnFaulted)
.ConfigureAwait(false);
} }
private async Task Listen() private async Task Listen()