Files
AutopilotMonitor/src/Backend/AutopilotMonitor.Functions/Middleware/TimingAllowOriginMiddleware.cs
T
Oliver Kieselbach 22427e24c5 perf(web,backend): finish off the duplicate-fetch + response-header fixes
Post-deploy re-validation in the browser surfaced two issues that the
previous commit did not actually solve:

- Timing-Allow-Origin was still missing on every API response (curl
  confirmed) and X-Correlation-ID was missing too — a preexisting bug.
  Both middlewares used httpContext.Response.OnStarting() to attach the
  header, but that callback is not reliably triggered in the .NET 8
  isolated worker: the host bridges the worker's response to the wire,
  so OnStarting fires on a shadow object that never reaches the client.
  UserRateLimitMiddleware already writes X-RateLimit-* directly on
  Response.Headers before calling next(), and those headers do come
  through in production. Switching both header-emitting middlewares
  to that pattern.

- /dashboard kept firing /api/config/<t>/feature-flags twice. The
  earlier dep-stabilization on useTenantSecurityConfig was incomplete:
  the effect fires once on initial render with user=null (deps
  [tenantId, undefined, undefined, undefined]) and once after AuthContext
  resolves user (deps [tenantId, bool, bool, role]) — two distinct dep
  tuples, two fetches. The user=null path also bypassed the role gate
  (the guard was `if (user && ...) return`). Adding `!user` to the
  early-return at the top of the effect collapses to one fetch and
  also drops the would-be 401 we were producing during the loading
  window.

Backend tests 1058/1058, web vitest 249/249, tsc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:01:09 +02:00

36 lines
1.5 KiB
C#

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Middleware;
namespace AutopilotMonitor.Functions.Middleware;
/// <summary>
/// Adds the <c>Timing-Allow-Origin: *</c> response header so cross-origin
/// callers (notably the portal) can read full Resource Timing details
/// (<c>transferSize</c>, <c>encodedBodySize</c>, <c>nextHopProtocol</c>, ...)
/// from the browser's <c>performance.getEntriesByType('resource')</c>.
/// Without this header those fields stay zero / "unknown" and our own
/// real-user monitoring cannot see API payload sizes.
/// </summary>
public class TimingAllowOriginMiddleware : IFunctionsWorkerMiddleware
{
private const string HeaderName = "Timing-Allow-Origin";
private const string HeaderValue = "*";
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
// Write directly on Response.Headers before next() — the
// OnStarting() callback is not reliably triggered in the .NET 8
// isolated worker (the host bridges the worker's response, so the
// hook fires on a shadow object that never reaches the wire). The
// direct-write pattern is the same one UserRateLimitMiddleware uses
// for X-RateLimit-* and is observed in production responses.
var httpContext = context.GetHttpContext();
if (httpContext != null && !httpContext.Response.Headers.ContainsKey(HeaderName))
{
httpContext.Response.Headers[HeaderName] = HeaderValue;
}
await next(context);
}
}