From 0a68a97291151624886e4e89632bfb8d902ab50d Mon Sep 17 00:00:00 2001 From: neckfire Date: Wed, 15 Jul 2026 09:02:45 +0200 Subject: [PATCH] =?UTF-8?q?fix(audit=20l=C3=A9ger):=20stats=20carri=C3=A8r?= =?UTF-8?q?e,=20pr=C3=A9sence=20amis,=20AddGenericId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GetPlayerStatisticsRequestsClient: renvoyait vide -> renvoie les stats demandées (0 pour l'instant, raid client-hosted non traçable) pour que l'écran carrière s'affiche. - UpdatePlayerPresenceState: persiste {inMatch,lastSeenUtc}; GetFriendList lit la présence et calcule onlineState (online si vu <3min, 2=en raid) au lieu de 0 en dur. - AddGenericId: persiste l'id de service lié (était un no-op). (Free-loadout non touché: S3 only, pas de free-loadout en S2.) Co-Authored-By: Claude Opus 4.8 --- .../Controllers/ClientController.cs | 12 ++++- .../CloudScript/Functions/GetFriendList.cs | 21 ++++++++- .../GetPlayerStatisticsRequestsClient.cs | 20 ++++---- .../Functions/UpdatePlayerPresenceState.cs | 47 +++++++++++++++++-- 4 files changed, 85 insertions(+), 15 deletions(-) diff --git a/src/Prospect.Server.Api/Controllers/ClientController.cs b/src/Prospect.Server.Api/Controllers/ClientController.cs index ff653a4..e8c6b00 100644 --- a/src/Prospect.Server.Api/Controllers/ClientController.cs +++ b/src/Prospect.Server.Api/Controllers/ClientController.cs @@ -140,8 +140,18 @@ public class ClientController : Controller [HttpPost("AddGenericID")] [Produces(MediaTypeNames.Application.Json)] [Authorize(AuthenticationSchemes = UserAuthenticationOptions.DefaultScheme)] - public IActionResult AddGenericId(FAddGenericIDRequest request) + public async Task AddGenericId(FAddGenericIDRequest request) { + // Persist the linked generic service id instead of dropping it (was a no-op stub). + var userId = User.FindAuthUserId(); + if (request.GenericId != null && !string.IsNullOrEmpty(request.GenericId.ServiceName)) + { + await _userDataService.UpdateAsync(userId, userId, new Dictionary + { + ["GenericId_" + request.GenericId.ServiceName] = request.GenericId.UserId ?? "" + }); + } + return Ok(new ClientResponse { Code = 200, diff --git a/src/Prospect.Server.Api/Services/CloudScript/Functions/GetFriendList.cs b/src/Prospect.Server.Api/Services/CloudScript/Functions/GetFriendList.cs index 0bc51b0..e5ee9ba 100644 --- a/src/Prospect.Server.Api/Services/CloudScript/Functions/GetFriendList.cs +++ b/src/Prospect.Server.Api/Services/CloudScript/Functions/GetFriendList.cs @@ -47,9 +47,28 @@ public class GetFriendList : ICloudScriptFunction if (steamIds.Count > 0) { var players = await _userService.FindManyAsync(PlayFabUserAuthType.Steam, steamIds); + var nowUtc = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); foreach (var player in players) { if (player.Id == userId) continue; // never list yourself + + // Presence persisted by UpdatePlayerPresenceState: online if seen in the last + // 3 minutes. EYUserState best-effort: 0 = offline, 1 = online, 2 = in match. + var onlineState = 0; + var presenceData = await _userDataService.FindAsync(player.Id, player.Id, new List { "PresenceState" }); + if (presenceData.TryGetValue("PresenceState", out var presenceRec) && !string.IsNullOrWhiteSpace(presenceRec.Value)) + { + try + { + var presence = JsonSerializer.Deserialize(presenceRec.Value); + if (presence != null && nowUtc - presence.LastSeenUtc <= 180) + { + onlineState = presence.InMatch ? 2 : 1; + } + } + catch { /* ignore malformed presence */ } + } + friends.Add(new { profile = new @@ -58,7 +77,7 @@ public class GetFriendList : ICloudScriptFunction displayName = player.DisplayName, avatarUrl = "", }, - onlineState = 0, // EYUserState — real presence tracking is a later phase + onlineState, }); } } diff --git a/src/Prospect.Server.Api/Services/CloudScript/Functions/GetPlayerStatisticsRequestsClient.cs b/src/Prospect.Server.Api/Services/CloudScript/Functions/GetPlayerStatisticsRequestsClient.cs index 7ee933a..fd42274 100644 --- a/src/Prospect.Server.Api/Services/CloudScript/Functions/GetPlayerStatisticsRequestsClient.cs +++ b/src/Prospect.Server.Api/Services/CloudScript/Functions/GetPlayerStatisticsRequestsClient.cs @@ -8,13 +8,8 @@ public class GetPlayerStatisticsRequestsClientRequest public string[] Statistics { get; set; } } -public class GetPlayerStatisticsRequestsClientResponse -{ - // TODO: Unknown structure -} - [CloudScriptFunction("GetPlayerStatisticsRequestsClient")] -public class GetPlayerStatisticsRequestsClientFunction : ICloudScriptFunction +public class GetPlayerStatisticsRequestsClientFunction : ICloudScriptFunction { private readonly IHttpContextAccessor _httpContextAccessor; @@ -23,7 +18,7 @@ public class GetPlayerStatisticsRequestsClientFunction : ICloudScriptFunction ExecuteAsync(GetPlayerStatisticsRequestsClientRequest request) + public Task ExecuteAsync(GetPlayerStatisticsRequestsClientRequest request) { var context = _httpContextAccessor.HttpContext; if (context == null) @@ -31,8 +26,15 @@ public class GetPlayerStatisticsRequestsClientFunction : ICloudScriptFunction(); + foreach (var name in request.Statistics ?? Array.Empty()) { - }; + stats.Add(new { statisticName = name, value = 0 }); + } + + return Task.FromResult(new { statistics = stats }); } } diff --git a/src/Prospect.Server.Api/Services/CloudScript/Functions/UpdatePlayerPresenceState.cs b/src/Prospect.Server.Api/Services/CloudScript/Functions/UpdatePlayerPresenceState.cs index d704b2e..f0a7524 100644 --- a/src/Prospect.Server.Api/Services/CloudScript/Functions/UpdatePlayerPresenceState.cs +++ b/src/Prospect.Server.Api/Services/CloudScript/Functions/UpdatePlayerPresenceState.cs @@ -1,12 +1,51 @@ -using Prospect.Server.Api.Services.CloudScript.Models; +using System.Text.Json; +using System.Text.Json.Serialization; +using Prospect.Server.Api.Services.Auth.Extensions; +using Prospect.Server.Api.Services.CloudScript.Models; +using Prospect.Server.Api.Services.UserData; namespace Prospect.Server.Api.Services.CloudScript.Functions; +// Persisted presence, read back by GetFriendList to show friends' online / in-match state. +public class PlayerPresenceState +{ + [JsonPropertyName("inMatch")] + public bool InMatch { get; set; } + [JsonPropertyName("lastSeenUtc")] + public long LastSeenUtc { get; set; } +} + [CloudScriptFunction("UpdatePlayerPresenceState")] public class UpdatePlayerPresenceState : ICloudScriptFunction { - public Task ExecuteAsync(FYUpdatePlayerPresenceStateRequest request) + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly UserDataService _userDataService; + + public UpdatePlayerPresenceState(IHttpContextAccessor httpContextAccessor, UserDataService userDataService) { - return Task.FromResult(new { }); + _httpContextAccessor = httpContextAccessor; + _userDataService = userDataService; } -} \ No newline at end of file + + public async Task ExecuteAsync(FYUpdatePlayerPresenceStateRequest request) + { + var context = _httpContextAccessor.HttpContext; + if (context == null) + { + return new { }; + } + + var userId = context.User.FindAuthUserId(); + var state = new PlayerPresenceState + { + InMatch = request.InMatch, + LastSeenUtc = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + }; + await _userDataService.UpdateAsync( + userId, userId, + new Dictionary { ["PresenceState"] = JsonSerializer.Serialize(state) } + ); + + return new { }; + } +}