using DeepDrftPublic; using MudBlazor.Services; using DeepDrftPublic.Components; using Microsoft.AspNetCore.HttpOverrides; using NetBlocks.Utilities.Environment; var builder = WebApplication.CreateBuilder(args); // Add MudBlazor services builder.Services.AddMudServices(); var apiPath = CredentialTools.ResolvePathOrThrow("api", "environment/api.json"); builder.Configuration.AddJsonFile(apiPath, optional: false, reloadOnChange: false); string apiUrl = builder.Configuration["Api:ContentApiUrl"] ?? throw new NullReferenceException("Api url is missing"); // Server-side, both named clients point straight at DeepDrftAPI (server-to-server, // no proxy hop). The TrackController below reuses the "DeepDrft.API" client to forward // the WASM client's public track calls upstream. DeepDrftPublic.Client.Startup.ConfigureApiHttpClient(builder.Services, apiUrl); DeepDrftPublic.Client.Startup.ConfigureContentServices(builder.Services, apiUrl); DeepDrftPublic.Client.Startup.ConfigureDomainServices(builder.Services); Startup.ConfigureDomainServices(builder); // Add services to the container. builder.Services.AddControllers(); builder.Services.AddCors(options => { options.AddPolicy("FramePlayerEmbedPolicy", policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); }); builder.Services.AddRazorComponents() .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents(); // Configure SignalR for better circuit cleanup builder.Services.AddSignalR(options => { if (builder.Environment.IsDevelopment()) { options.EnableDetailedErrors = true; options.KeepAliveInterval = TimeSpan.FromSeconds(10); options.ClientTimeoutInterval = TimeSpan.FromSeconds(30); } }); // Configure forwarded headers for reverse proxy support builder.Services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost; // Trust any proxy (nginx) - in production, specify known proxy networks options.KnownNetworks.Clear(); options.KnownProxies.Clear(); }); var app = builder.Build(); // Configure the HTTP request pipeline. // Use forwarded headers before other middleware app.UseForwardedHeaders(); if (app.Environment.IsDevelopment()) { app.UseWebAssemblyDebugging(); } else { app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); // Only use HTTPS redirection if not behind a reverse proxy var disableHttpsRedirection = app.Configuration.GetValue("ForwardedHeaders:DisableHttpsRedirection"); if (!disableHttpsRedirection) { app.UseHttpsRedirection(); } } app.UseCors("FramePlayerEmbedPolicy"); // For requests to /FramePlayer, remove any X-Frame-Options header and set a permissive // frame-ancestors CSP so the page can be embedded in iframes on any external domain. // OnStarting fires just before headers are flushed, ensuring this overrides headers set // by other middleware (e.g. HSTS, reverse proxy). app.Use(async (context, next) => { if (context.Request.Path.StartsWithSegments("/FramePlayer", StringComparison.OrdinalIgnoreCase)) { context.Response.OnStarting(() => { context.Response.Headers.Remove("X-Frame-Options"); context.Response.Headers["Content-Security-Policy"] = "frame-ancestors *"; return Task.CompletedTask; }); } await next(); }); // Antiforgery is required by Blazor form handling. Authentication / authorization // middleware is intentionally absent — this host is fully anonymous. app.UseAntiforgery(); // UseStaticFiles runs before endpoint dispatch and ensures wwwroot/ files (e.g. /js/audio/*.js) // are served with correct Content-Type. MapStaticAssets alone can drop the header on // compressed assets; removing this call breaks the audio interop module in production. app.UseStaticFiles(); // Configure cache headers for Blazor WebAssembly assets if (app.Environment.IsDevelopment()) { app.Use(async (context, next) => { if (context.Request.Path.StartsWithSegments("/_framework") || context.Request.Path.StartsWithSegments("/_content")) { context.Response.Headers.CacheControl = "no-cache, no-store, must-revalidate"; context.Response.Headers.Pragma = "no-cache"; context.Response.Headers.Expires = "0"; } await next(); }); } app.MapStaticAssets(); // Serve TypeScript source files for debugging in development if (app.Environment.IsDevelopment()) { app.UseStaticFiles(new StaticFileOptions { FileProvider = new Microsoft.Extensions.FileProviders.PhysicalFileProvider( Path.Combine(app.Environment.ContentRootPath, "Interop")), RequestPath = "/Interop" }); } app.MapControllers(); app.MapRazorComponents() .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode() .AddAdditionalAssemblies(typeof(DeepDrftPublic.Client._Imports).Assembly); app.UseStatusCodePagesWithReExecute("/404", createScopeForStatusCodePages: true); app.Run();