using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using SVSim.BattleNode.Bridge;
using SVSim.BattleNode.Sessions;
namespace SVSim.BattleNode.Hosting;
///
/// Registration + pipeline extensions that turn an arbitrary ASP.NET Core host into a battle
/// node. The library has no dependency on any specific host project — call both methods from
/// wherever you build your .
///
public static class BattleNodeExtensions
{
///
/// Register the battle node's services in DI. All four are singletons because none of them
/// carry per-request state — per-battle state lives on the
/// instance the WebSocket handler constructs on connect.
///
///
/// Callback to populate . Must set
/// — there is no hardcoded fallback; the
/// value is a deployment concern (localhost during dev, a real host[:port]/socket.io/
/// when the node runs behind a reverse proxy or on a separate box). Startup throws
/// if it's still empty after the callback runs.
///
///
/// is null, empty, or whitespace after
/// runs.
///
public static IServiceCollection AddBattleNode(this IServiceCollection services, Action? configure = null)
{
var options = new BattleNodeOptions();
configure?.Invoke(options);
if (string.IsNullOrWhiteSpace(options.NodeServerUrl))
{
throw new InvalidOperationException(
"BattleNode:NodeServerUrl is not configured. Set it in appsettings.json under " +
"the \"BattleNode\" section (format: \"host[:port]/socket.io/\", no scheme prefix).");
}
services.AddSingleton(options);
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
return services;
}
///
/// Wire up the WebSocket middleware and map the Socket.IO endpoint at /socket.io/.
/// Call this AFTER any HTTP middleware that should still see non-WS requests (auth,
/// routing, controllers) and BEFORE MapControllers(). The endpoint accepts any
/// path under /socket.io; the handler doesn't read the sub-path, so default
/// Socket.IO clients targeting /socket.io/?EIO=3&transport=websocket work
/// without configuration.
///
///
/// Steam auth gets a free pass on WS upgrades — see
/// SteamSessionAuthenticationHandler's header-based bypass. The node has its own
/// per-connection auth (encrypted viewerId in the upgrade headers, validated against the
/// matched battle id in ).
///
public static IApplicationBuilder UseBattleNode(this IApplicationBuilder app)
{
app.UseWebSockets();
app.Map("/socket.io", branch => branch.Run(HandleSocketIoAsync));
return app;
}
///
/// Terminal handler for /socket.io/* — resolves the singleton
/// from DI and hands the request over.
/// Extracted from the inline lambda in so stack traces
/// show a real method name during WS connect failures.
///
private static async Task HandleSocketIoAsync(Microsoft.AspNetCore.Http.HttpContext ctx)
{
var handler = ctx.RequestServices.GetRequiredService();
await handler.HandleAsync(ctx);
}
}