304 lines
10 KiB
C#
304 lines
10 KiB
C#
using Microsoft.JSInterop;
|
|
using RpgRoller.Contracts;
|
|
|
|
namespace RpgRoller.Components.Pages;
|
|
|
|
public sealed class WorkspaceSessionCoordinator
|
|
{
|
|
public WorkspaceSessionCoordinator(
|
|
WorkspaceState state,
|
|
WorkspaceFeedbackService feedback,
|
|
IJSRuntime js,
|
|
RpgRollerApiClient apiClient,
|
|
WorkspaceQueryService workspaceQuery,
|
|
Func<Guid?, Task> reloadCampaignsAsync,
|
|
Func<Task> reloadCharacterCampaignOptionsAsync,
|
|
Func<Task> refreshCampaignScopeAsync,
|
|
Func<Task> syncStateEventsAsync,
|
|
Func<Task> stopStateEventsAsync,
|
|
Func<Task> ensureAdminUsersLoadedAsync,
|
|
Action resetCampaignLogDetailState,
|
|
Func<Task> requestRefreshAsync,
|
|
Func<string?, Task> onLoggedOutAsync)
|
|
{
|
|
m_State = state;
|
|
m_Feedback = feedback;
|
|
m_JS = js;
|
|
m_ApiClient = apiClient;
|
|
m_WorkspaceQuery = workspaceQuery;
|
|
m_ReloadCampaignsAsync = reloadCampaignsAsync;
|
|
m_ReloadCharacterCampaignOptionsAsync = reloadCharacterCampaignOptionsAsync;
|
|
m_RefreshCampaignScopeAsync = refreshCampaignScopeAsync;
|
|
m_SyncStateEventsAsync = syncStateEventsAsync;
|
|
m_StopStateEventsAsync = stopStateEventsAsync;
|
|
m_EnsureAdminUsersLoadedAsync = ensureAdminUsersLoadedAsync;
|
|
m_ResetCampaignLogDetailState = resetCampaignLogDetailState;
|
|
m_RequestRefreshAsync = requestRefreshAsync;
|
|
m_OnLoggedOutAsync = onLoggedOutAsync;
|
|
}
|
|
|
|
public async Task InitializeAsync()
|
|
{
|
|
var storedScreen = await m_JS.InvokeAsync<string?>("rpgRollerApi.getSessionValue", ScreenSessionKey);
|
|
m_State.CurrentScreen = NormalizeRequestedScreen(storedScreen) ?? ScreenPlay;
|
|
|
|
var storedPanel = await m_JS.InvokeAsync<string?>("rpgRollerApi.getSessionValue", MobilePanelSessionKey);
|
|
if (string.Equals(storedPanel, "log", StringComparison.OrdinalIgnoreCase))
|
|
m_State.MobilePanel = "log";
|
|
|
|
var storedRollVisibility = await m_JS.InvokeAsync<string?>("rpgRollerApi.getSessionValue", RollVisibilitySessionKey);
|
|
m_State.RollVisibility = NormalizeRollVisibility(storedRollVisibility);
|
|
|
|
Guid? preferredCampaignId = null;
|
|
var storedCampaignId = await m_JS.InvokeAsync<string?>("rpgRollerApi.getSessionValue", CampaignSessionKey);
|
|
if (Guid.TryParse(storedCampaignId, out var parsedCampaignId))
|
|
preferredCampaignId = parsedCampaignId;
|
|
|
|
await CheckHealthAsync();
|
|
await LoadRulesetsAsync();
|
|
|
|
var reloaded = await ReloadAuthenticatedSessionAsync(preferredCampaignId);
|
|
if (!reloaded)
|
|
await m_OnLoggedOutAsync("Session expired. Please log in again.");
|
|
}
|
|
|
|
public async Task RetryAfterHealthIssueAsync()
|
|
{
|
|
await CheckHealthAsync();
|
|
if (!m_State.HasHealthIssue && m_State.User is not null)
|
|
{
|
|
var reloaded = await ReloadAuthenticatedSessionAsync(m_State.SelectedCampaignId);
|
|
if (!reloaded)
|
|
await m_OnLoggedOutAsync("Session expired. Please log in again.");
|
|
}
|
|
}
|
|
|
|
public async Task LoadKnownUsernamesAsync()
|
|
{
|
|
try
|
|
{
|
|
var usernames = await m_WorkspaceQuery.GetUsernamesAsync();
|
|
m_State.KnownUsernames = usernames.OrderBy(username => username, StringComparer.OrdinalIgnoreCase).ToList();
|
|
}
|
|
catch (ApiRequestException ex)
|
|
{
|
|
m_State.KnownUsernames = [];
|
|
m_Feedback.SetStatus(ex.Message, true);
|
|
}
|
|
}
|
|
|
|
public async Task LogoutAsync()
|
|
{
|
|
if (m_State.IsMutating)
|
|
return;
|
|
|
|
m_State.IsMutating = true;
|
|
try
|
|
{
|
|
await m_ApiClient.RequestWithoutPayloadAsync("POST", "/api/auth/logout");
|
|
}
|
|
catch (ApiRequestException)
|
|
{
|
|
}
|
|
finally
|
|
{
|
|
m_State.IsMutating = false;
|
|
}
|
|
|
|
ClearAuthenticatedState();
|
|
await m_StopStateEventsAsync();
|
|
await m_OnLoggedOutAsync("Logged out.");
|
|
}
|
|
|
|
public async Task SwitchScreenAsync(string screen)
|
|
{
|
|
var targetScreen = NormalizeRequestedScreen(screen) ?? ScreenPlay;
|
|
if (string.Equals(targetScreen, ScreenAdmin, StringComparison.OrdinalIgnoreCase) && !m_State.IsCurrentUserAdmin)
|
|
targetScreen = ScreenPlay;
|
|
|
|
m_State.CurrentScreen = targetScreen;
|
|
m_State.IsScreenMenuOpen = false;
|
|
await PersistScreenPreferenceAsync(m_State.CurrentScreen);
|
|
await m_RequestRefreshAsync();
|
|
|
|
if (m_State.User is not null)
|
|
{
|
|
await m_RefreshCampaignScopeAsync();
|
|
await m_SyncStateEventsAsync();
|
|
}
|
|
|
|
if (m_State.IsAdminScreen)
|
|
{
|
|
await m_EnsureAdminUsersLoadedAsync();
|
|
await m_RequestRefreshAsync();
|
|
}
|
|
}
|
|
|
|
public async Task OnRollVisibilityChangedAsync(string visibility)
|
|
{
|
|
m_State.RollVisibility = NormalizeRollVisibility(visibility);
|
|
await m_JS.InvokeVoidAsync("rpgRollerApi.setSessionValue", RollVisibilitySessionKey, m_State.RollVisibility);
|
|
}
|
|
|
|
public void ClearAuthenticatedState()
|
|
{
|
|
m_State.User = null;
|
|
m_State.ActiveCharacterId = null;
|
|
m_State.SelectedCampaignId = null;
|
|
m_State.SelectedCampaign = null;
|
|
m_State.Campaigns = [];
|
|
m_State.CharacterCampaignOptions = [];
|
|
m_State.SelectedCharacterSkills = [];
|
|
m_State.SelectedCharacterSkillGroups = [];
|
|
m_State.CampaignLog = [];
|
|
m_State.CampaignLogCursor = null;
|
|
m_ResetCampaignLogDetailState();
|
|
m_State.SelectedCharacterId = null;
|
|
m_State.LastRoll = null;
|
|
m_State.KnownUsernames = [];
|
|
m_State.ShowCreateCharacterModal = false;
|
|
m_State.ShowEditCharacterModal = false;
|
|
m_State.CanEditCharacterOwner = false;
|
|
m_State.CreateCharacterInitialModel = new();
|
|
m_State.EditCharacterInitialModel = new();
|
|
m_State.CreateCharacterFormVersion = 0;
|
|
m_State.EditCharacterFormVersion = 0;
|
|
m_State.AdminUsers = [];
|
|
m_State.HasLoadedAdminUsers = false;
|
|
m_State.IsAdminDataLoading = false;
|
|
m_Feedback.ClearToasts();
|
|
}
|
|
|
|
private async Task CheckHealthAsync()
|
|
{
|
|
m_State.HasHealthIssue = false;
|
|
m_State.HealthIssueMessage = string.Empty;
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
private async Task LoadRulesetsAsync()
|
|
{
|
|
try
|
|
{
|
|
m_State.Rulesets = (await m_WorkspaceQuery.GetRulesetsAsync()).ToList();
|
|
}
|
|
catch (ApiRequestException ex)
|
|
{
|
|
m_Feedback.SetStatus(ex.Message, true);
|
|
}
|
|
}
|
|
|
|
private async Task<bool> ReloadAuthenticatedSessionAsync(Guid? preferredCampaignId)
|
|
{
|
|
var me = await TryGetMeAsync();
|
|
if (me is null)
|
|
{
|
|
ClearAuthenticatedState();
|
|
await m_StopStateEventsAsync();
|
|
return false;
|
|
}
|
|
|
|
m_State.User = me.User;
|
|
m_State.ActiveCharacterId = me.ActiveCharacterId;
|
|
await EnsureScreenAccessAsync();
|
|
|
|
await m_ReloadCampaignsAsync(preferredCampaignId ?? me.CurrentCampaignId);
|
|
await m_ReloadCharacterCampaignOptionsAsync();
|
|
await m_RefreshCampaignScopeAsync();
|
|
await m_SyncStateEventsAsync();
|
|
|
|
if (m_State.IsAdminScreen)
|
|
await m_EnsureAdminUsersLoadedAsync();
|
|
|
|
return true;
|
|
}
|
|
|
|
private async Task<MeResponse?> TryGetMeAsync()
|
|
{
|
|
try
|
|
{
|
|
return await m_WorkspaceQuery.GetMeAsync();
|
|
}
|
|
catch (ApiRequestException ex) when (ex.StatusCode == 401)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private async Task EnsureScreenAccessAsync()
|
|
{
|
|
if (m_State.IsCurrentUserAdmin)
|
|
return;
|
|
|
|
m_State.AdminUsers = [];
|
|
m_State.HasLoadedAdminUsers = false;
|
|
|
|
if (!m_State.IsAdminScreen)
|
|
return;
|
|
|
|
m_State.CurrentScreen = ScreenPlay;
|
|
await PersistScreenPreferenceAsync(m_State.CurrentScreen);
|
|
}
|
|
|
|
private async Task PersistScreenPreferenceAsync(string screen)
|
|
{
|
|
try
|
|
{
|
|
await m_JS.InvokeVoidAsync("rpgRollerApi.setSessionValue", ScreenSessionKey, screen);
|
|
}
|
|
catch (JSDisconnectedException)
|
|
{
|
|
}
|
|
catch (InvalidOperationException ex) when (IsStaticRenderInteropException(ex))
|
|
{
|
|
}
|
|
}
|
|
|
|
private static string NormalizeRollVisibility(string? visibility)
|
|
{
|
|
return string.Equals(visibility, "private", StringComparison.OrdinalIgnoreCase) ? "private" : "public";
|
|
}
|
|
|
|
private static string? NormalizeRequestedScreen(string? screen)
|
|
{
|
|
if (string.Equals(screen, ScreenAdmin, StringComparison.OrdinalIgnoreCase))
|
|
return ScreenAdmin;
|
|
|
|
if (string.Equals(screen, ScreenManagement, StringComparison.OrdinalIgnoreCase))
|
|
return ScreenManagement;
|
|
|
|
if (string.Equals(screen, ScreenPlay, StringComparison.OrdinalIgnoreCase))
|
|
return ScreenPlay;
|
|
|
|
return null;
|
|
}
|
|
|
|
private static bool IsStaticRenderInteropException(InvalidOperationException exception)
|
|
{
|
|
return exception.Message.Contains("JavaScript interop calls cannot be issued", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private readonly RpgRollerApiClient m_ApiClient;
|
|
private readonly WorkspaceFeedbackService m_Feedback;
|
|
private readonly Func<Task> m_EnsureAdminUsersLoadedAsync;
|
|
private readonly IJSRuntime m_JS;
|
|
private readonly Func<string?, Task> m_OnLoggedOutAsync;
|
|
private readonly Func<Task> m_ReloadCharacterCampaignOptionsAsync;
|
|
private readonly Func<Guid?, Task> m_ReloadCampaignsAsync;
|
|
private readonly Action m_ResetCampaignLogDetailState;
|
|
private readonly Func<Task> m_RefreshCampaignScopeAsync;
|
|
private readonly Func<Task> m_RequestRefreshAsync;
|
|
private readonly WorkspaceState m_State;
|
|
private readonly Func<Task> m_StopStateEventsAsync;
|
|
private readonly Func<Task> m_SyncStateEventsAsync;
|
|
private readonly WorkspaceQueryService m_WorkspaceQuery;
|
|
|
|
private const string ScreenPlay = "play";
|
|
private const string ScreenManagement = "management";
|
|
private const string ScreenAdmin = "admin";
|
|
private const string ScreenSessionKey = "screen";
|
|
private const string CampaignSessionKey = "campaign";
|
|
private const string MobilePanelSessionKey = "play-panel";
|
|
private const string RollVisibilitySessionKey = "roll-visibility";
|
|
}
|