Files
RpgRoller/RpgRoller/Components/Pages/WorkspaceSessionCoordinator.cs
2026-05-04 22:11:20 +02:00

215 lines
6.6 KiB
C#

using Microsoft.JSInterop;
using RpgRoller.Contracts;
namespace RpgRoller.Components.Pages;
public sealed class WorkspaceSessionCoordinator(
WorkspaceState state,
WorkspaceFeedbackService feedback,
IJSRuntime js,
RpgRollerApiClient apiClient,
WorkspaceQueryService workspaceQuery,
Func<bool> isAdminRoute,
Func<Task> redirectToPlayAsync,
Func<Guid?, Task> reloadCampaignsAsync,
Func<Task> reloadCharacterCampaignOptionsAsync,
Func<Task> refreshCampaignScopeAsync,
Func<Task> syncStateEventsAsync,
Func<Task> stopStateEventsAsync,
Func<Task> ensureAdminUsersLoadedAsync,
Action resetCampaignLogDetailState,
Func<string?, Task> onLoggedOutAsync)
{
public async Task InitializeAsync()
{
var storedPanel = await js.InvokeAsync<string?>("rpgRollerApi.getSessionValue", MobilePanelSessionKey);
if (string.Equals(storedPanel, "log", StringComparison.OrdinalIgnoreCase))
state.MobilePanel = "log";
var storedRollVisibility =
await js.InvokeAsync<string?>("rpgRollerApi.getSessionValue", RollVisibilitySessionKey);
state.RollVisibility = NormalizeRollVisibility(storedRollVisibility);
Guid? preferredCampaignId = null;
if (!isAdminRoute())
{
var storedCampaignId = await js.InvokeAsync<string?>("rpgRollerApi.getSessionValue", CampaignSessionKey);
if (Guid.TryParse(storedCampaignId, out var parsedCampaignId))
preferredCampaignId = parsedCampaignId;
}
await CheckHealthAsync();
var reloaded = await ReloadAuthenticatedSessionAsync(preferredCampaignId);
if (!reloaded)
await onLoggedOutAsync("Session expired. Please log in again.");
}
public async Task RetryAfterHealthIssueAsync()
{
await CheckHealthAsync();
if (!state.HasHealthIssue && state.User is not null)
{
var reloaded = await ReloadAuthenticatedSessionAsync(state.SelectedCampaignId);
if (!reloaded)
await onLoggedOutAsync("Session expired. Please log in again.");
}
}
public async Task LoadKnownUsernamesAsync()
{
try
{
var usernames = await workspaceQuery.GetUsernamesAsync();
state.KnownUsernames = usernames.OrderBy(username => username, StringComparer.OrdinalIgnoreCase).ToList();
}
catch (ApiRequestException ex)
{
state.KnownUsernames = [];
feedback.SetStatus(ex.Message, true);
}
}
public async Task LogoutAsync()
{
if (state.IsMutating)
return;
state.IsMutating = true;
try
{
await apiClient.RequestWithoutPayloadAsync("POST", "/api/auth/logout");
}
catch (ApiRequestException)
{
}
finally
{
state.IsMutating = false;
}
ClearAuthenticatedState();
await stopStateEventsAsync();
await onLoggedOutAsync("Logged out.");
}
public async Task OnRollVisibilityChangedAsync(string visibility)
{
state.RollVisibility = NormalizeRollVisibility(visibility);
await js.InvokeVoidAsync("rpgRollerApi.setSessionValue", RollVisibilitySessionKey, state.RollVisibility);
}
public void ClearAuthenticatedState()
{
state.User = null;
state.ActiveCharacterId = null;
state.SelectedCampaignId = null;
state.SelectedCampaign = null;
state.Campaigns = [];
state.CharacterCampaignOptions = [];
state.SelectedCharacterSkills = [];
state.SelectedCharacterSkillGroups = [];
state.CampaignLog = [];
state.CampaignLogCursor = null;
resetCampaignLogDetailState();
state.SelectedCharacterId = null;
state.LastRoll = null;
state.KnownUsernames = [];
state.ShowCreateCharacterModal = false;
state.ShowEditCharacterModal = false;
state.CanEditCharacterOwner = false;
state.CreateCharacterInitialModel = new();
state.EditCharacterInitialModel = new();
state.CreateCharacterFormVersion = 0;
state.EditCharacterFormVersion = 0;
state.AdminUsers = [];
state.HasLoadedAdminUsers = false;
state.IsAdminDataLoading = false;
feedback.ClearToasts();
}
private async Task CheckHealthAsync()
{
state.HasHealthIssue = false;
state.HealthIssueMessage = string.Empty;
await Task.CompletedTask;
}
private async Task LoadRulesetsAsync()
{
try
{
state.Rulesets = (await workspaceQuery.GetRulesetsAsync()).ToList();
}
catch (ApiRequestException ex)
{
feedback.SetStatus(ex.Message, true);
}
}
private async Task<bool> ReloadAuthenticatedSessionAsync(Guid? preferredCampaignId)
{
var me = await TryGetMeAsync();
if (me is null)
{
ClearAuthenticatedState();
await stopStateEventsAsync();
return false;
}
state.User = me.User;
state.ActiveCharacterId = me.ActiveCharacterId;
if (!await EnsureRouteAccessAsync())
return true;
if (isAdminRoute())
{
await stopStateEventsAsync();
state.ConnectionState = "offline";
await ensureAdminUsersLoadedAsync();
return true;
}
await LoadRulesetsAsync();
await reloadCampaignsAsync(preferredCampaignId ?? me.CurrentCampaignId);
await reloadCharacterCampaignOptionsAsync();
await refreshCampaignScopeAsync();
await syncStateEventsAsync();
return true;
}
private async Task<MeResponse?> TryGetMeAsync()
{
try
{
return await workspaceQuery.GetMeAsync();
}
catch (ApiRequestException ex) when (ex.StatusCode == 401)
{
return null;
}
}
private async Task<bool> EnsureRouteAccessAsync()
{
if (state.IsCurrentUserAdmin || !isAdminRoute())
{
return true;
}
state.AdminUsers = [];
state.HasLoadedAdminUsers = false;
await redirectToPlayAsync();
return false;
}
private static string NormalizeRollVisibility(string? visibility)
{
return string.Equals(visibility, "private", StringComparison.OrdinalIgnoreCase) ? "private" : "public";
}
private const string CampaignSessionKey = "campaign";
private const string MobilePanelSessionKey = "play-panel";
private const string RollVisibilitySessionKey = "roll-visibility";
}