using System.Diagnostics; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; using Nexus.Api.Models; using Nexus.Api.Observability; namespace Nexus.Api.Controllers; [Authorize] [ApiController] [Route("api/v1/telemetry/browser")] [EnableRateLimiting("agents")] public sealed class BrowserTelemetryController : ControllerBase { private static readonly HashSet AllowedNames = [ "CLS", "FCP", "INP", "LCP", "TTFB", "board_content_visible", "board_delta_painted", "mutation_confirmed", "agent_proposal_readback" ]; private static readonly HashSet AllowedRatings = ["good", "needs-improvement", "poor", "custom"]; [HttpPost] public IResult Record([FromBody] BrowserMetricRequest request) { if (!AllowedNames.Contains(request.Name)) return Results.ValidationProblem(new Dictionary { ["name"] = ["Unsupported browser metric."] }); if (!double.IsFinite(request.Value) || request.Value < 0 || request.Value > 86_400_000) return Results.ValidationProblem(new Dictionary { ["value"] = ["Metric value must be finite and within the accepted range."] }); if (!AllowedRatings.Contains(request.Rating)) return Results.ValidationProblem(new Dictionary { ["rating"] = ["Unsupported metric rating."] }); var route = NormalizeBoundedDimension(request.RouteName, "unknown", 80); var liveMode = request.LiveMode is "live" or "polling" ? request.LiveMode : "unknown"; var navigationType = NormalizeBoundedDimension(request.NavigationType, "unknown", 40); var tags = new TagList { { "metric.name", request.Name }, { "metric.rating", request.Rating }, { "route.name", route }, { "nexus.live_mode", liveMode }, { "navigation.type", navigationType } }; if (request.Name == "CLS") NexusTelemetry.BrowserScore.Record(request.Value, tags); else NexusTelemetry.BrowserDuration.Record(request.Value, tags); return Results.NoContent(); } private static string NormalizeBoundedDimension(string? value, string fallback, int maxLength) { if (string.IsNullOrWhiteSpace(value)) return fallback; var normalized = new string(value .Where(character => char.IsAsciiLetterOrDigit(character) || character is ' ' or '_' or '-') .Take(maxLength) .ToArray()); return string.IsNullOrWhiteSpace(normalized) ? fallback : normalized; } }