From 1bdcd8397ba0cb94497c8c46270ced76f2eb722e Mon Sep 17 00:00:00 2001 From: gamer147 Date: Sat, 4 Jul 2026 15:10:23 -0400 Subject: [PATCH] config(battle-node): require NodeServerUrl from appsettings, no fallback Move the localhost default off BattleNodeOptions and out of Program.cs into an appsettings.json "BattleNode" section, and make AddBattleNode throw at startup if the value is missing or blank. Deployments now must set the URL explicitly, and dev keeps working via the checked-in value. Co-Authored-By: Claude Opus 4.7 --- SVSim.BattleNode/Bridge/BattleNodeOptions.cs | 20 +++++++++++++++---- .../Hosting/BattleNodeExtensions.cs | 19 ++++++++++++++---- SVSim.EmulatedEntrypoint/Program.cs | 9 ++++----- SVSim.EmulatedEntrypoint/appsettings.json | 8 ++++++++ 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/SVSim.BattleNode/Bridge/BattleNodeOptions.cs b/SVSim.BattleNode/Bridge/BattleNodeOptions.cs index 1cd35971..b9f7d374 100644 --- a/SVSim.BattleNode/Bridge/BattleNodeOptions.cs +++ b/SVSim.BattleNode/Bridge/BattleNodeOptions.cs @@ -1,13 +1,25 @@ namespace SVSim.BattleNode.Bridge; /// -/// DI-injected options for the battle node. NodeServerUrl matches the prod -/// do_matching wire format: host:port/socket.io/, no scheme prefix. -/// BestHTTP's SocketManager parses it as the Socket.IO v2 endpoint URL. +/// DI-injected options for the battle node. /// public sealed class BattleNodeOptions { - public string NodeServerUrl { get; set; } = "localhost:5148/socket.io/"; + /// + /// The Socket.IO v2 endpoint URL echoed back to the client on /*/do_matching + /// success (matching_state 3004/3007/3011). Matches the prod do_matching wire format: + /// host[:port]/socket.io/ — no scheme prefix, must end with /socket.io/. + /// The client's BestHTTP SocketManager parses this string directly; a leading + /// http:///https:// or a missing trailing slash will make the client + /// fail to connect. Host may be an IP, hostname, or FQDN. + /// + /// Deployment-time value — no hardcoded fallback. Must be provided via the + /// "BattleNode:NodeServerUrl" key in appsettings.json (or an + /// equivalently-named env var); + /// validates presence at startup and throws if empty. + /// + /// + public string NodeServerUrl { get; set; } = ""; /// /// How long the first arriver's WS waits for a partner before disconnecting. diff --git a/SVSim.BattleNode/Hosting/BattleNodeExtensions.cs b/SVSim.BattleNode/Hosting/BattleNodeExtensions.cs index 8c07fb8f..f2aa5125 100644 --- a/SVSim.BattleNode/Hosting/BattleNodeExtensions.cs +++ b/SVSim.BattleNode/Hosting/BattleNodeExtensions.cs @@ -18,15 +18,26 @@ public static class BattleNodeExtensions /// instance the WebSocket handler constructs on connect. /// /// - /// Optional callback to override defaults. The default - /// NodeServerUrl assumes the EmulatedEntrypoint host on - /// http://localhost:5148 and shares the port for the Socket.IO endpoint. Override - /// when the node runs on a different port/host or behind a reverse proxy. + /// 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(); diff --git a/SVSim.EmulatedEntrypoint/Program.cs b/SVSim.EmulatedEntrypoint/Program.cs index 2acb5338..6c5c9164 100644 --- a/SVSim.EmulatedEntrypoint/Program.cs +++ b/SVSim.EmulatedEntrypoint/Program.cs @@ -165,11 +165,10 @@ public class Program builder.Services.AddBattleNode(opt => { - // Matches the prod do_matching wire format: host:port/socket.io/, no scheme prefix. - // BestHTTP's SocketManager parses this as the Socket.IO v2 endpoint URL. - opt.NodeServerUrl = "localhost:5148/socket.io/"; - // Any field in BattleNodeOptions can be overridden via the "BattleNode" section - // in appsettings*.json — see appsettings.Development.json for DiagnosticLogging. + // Every field on BattleNodeOptions is populated from the "BattleNode" section in + // appsettings*.json. NodeServerUrl has no hardcoded fallback — AddBattleNode + // throws at startup if it's missing/blank. See BattleNodeOptions.NodeServerUrl + // for the required wire format. builder.Configuration.GetSection("BattleNode").Bind(opt); }); // In-process FCFS pair-up for TK2 PvP /do_matching, plus rank-battle's AI-fallback diff --git a/SVSim.EmulatedEntrypoint/appsettings.json b/SVSim.EmulatedEntrypoint/appsettings.json index dee87515..d0efef73 100644 --- a/SVSim.EmulatedEntrypoint/appsettings.json +++ b/SVSim.EmulatedEntrypoint/appsettings.json @@ -27,5 +27,13 @@ "Deck": { "MaxDeckSlots": 36 }, + "BattleNode": { + // Socket.IO v2 endpoint URL echoed to the client on /*/do_matching success. + // Wire format: "host[:port]/socket.io/" — no scheme prefix, trailing slash REQUIRED. + // Host may be an IP, hostname, or FQDN. BestHTTP's SocketManager parses this string + // directly; a leading http:// or https:// will make the client fail to connect. + // Startup throws if this value is missing or blank; there is no hardcoded fallback. + "NodeServerUrl": "localhost:5148/socket.io/" + }, "AllowedHosts": "*" }