63 lines
2.7 KiB
C#
63 lines
2.7 KiB
C#
using RpgRoller.Contracts;
|
|
using RpgRoller.Services;
|
|
|
|
namespace RpgRoller.Api;
|
|
|
|
internal static class StateEventEndpoints
|
|
{
|
|
public static RouteGroupBuilder MapStateEventEndpoints(this RouteGroupBuilder group)
|
|
{
|
|
group.MapGet("/events/state", async Task<IResult> (Guid campaignId, HttpContext context, IGameService game) =>
|
|
{
|
|
var sessionToken = context.GetRequiredSessionToken();
|
|
var stateResult = game.GetCampaignStateSnapshot(sessionToken, campaignId);
|
|
if (!stateResult.Succeeded)
|
|
return stateResult.Error!.Code == "unauthorized" ? TypedResults.Unauthorized() : TypedResults.BadRequest(new ApiError(stateResult.Error.Message, stateResult.Error.Code));
|
|
|
|
context.Response.Headers.CacheControl = "no-cache";
|
|
context.Response.Headers.Connection = "keep-alive";
|
|
context.Response.ContentType = "text/event-stream";
|
|
|
|
var lastState = stateResult.Value!;
|
|
await WriteStateEventAsync(context.Response, lastState);
|
|
await context.Response.Body.FlushAsync();
|
|
|
|
try
|
|
{
|
|
while (!context.RequestAborted.IsCancellationRequested)
|
|
{
|
|
await Task.Delay(TimeSpan.FromSeconds(1), context.RequestAborted);
|
|
|
|
var currentStateResult = game.GetCampaignStateSnapshot(sessionToken, campaignId);
|
|
if (!currentStateResult.Succeeded)
|
|
break;
|
|
|
|
var currentState = currentStateResult.Value!;
|
|
if (currentState.TotalVersion != lastState.TotalVersion)
|
|
{
|
|
lastState = currentState;
|
|
await WriteStateEventAsync(context.Response, currentState);
|
|
}
|
|
else
|
|
await context.Response.WriteAsync(": heartbeat\n\n");
|
|
|
|
await context.Response.Body.FlushAsync();
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
|
|
return TypedResults.Empty;
|
|
});
|
|
|
|
return group;
|
|
}
|
|
|
|
private static Task WriteStateEventAsync(HttpResponse response, CampaignStateSnapshot snapshot)
|
|
{
|
|
var characterVersions = string.Join(",", snapshot.CharacterVersions.Select(version => $"{{\"characterId\":\"{version.CharacterId}\",\"version\":{version.Version}}}"));
|
|
|
|
return response.WriteAsync($"event: state\ndata: {{\"campaignId\":\"{snapshot.CampaignId}\",\"totalVersion\":{snapshot.TotalVersion},\"rosterVersion\":{snapshot.RosterVersion},\"logVersion\":{snapshot.LogVersion},\"characterVersions\":[{characterVersions}]}}\n\n");
|
|
}
|
|
} |