diff --git a/src/Prospect.Server.Api/Services/CloudScript/Functions/GetSignalRConnection.cs b/src/Prospect.Server.Api/Services/CloudScript/Functions/GetSignalRConnection.cs index 4808610..4e404bf 100644 --- a/src/Prospect.Server.Api/Services/CloudScript/Functions/GetSignalRConnection.cs +++ b/src/Prospect.Server.Api/Services/CloudScript/Functions/GetSignalRConnection.cs @@ -1,4 +1,6 @@ -using Microsoft.Extensions.Options; +using System.Net; +using System.Net.Sockets; +using Microsoft.Extensions.Options; using Prospect.Server.Api.Config; using Prospect.Server.Api.Services.CloudScript.Models; @@ -7,20 +9,52 @@ namespace Prospect.Server.Api.Services.CloudScript.Functions; [CloudScriptFunction("GetSignalRConnection")] public class GetSignalRConnection : ICloudScriptFunction { - private readonly PlayFabSettings _settings; + // LAN address of the homelab host. Clients on the local network get the SignalR URL on + // this IP (which their cert already validates via the IP/DNS SAN), while external clients + // keep the public domain from configuration. This matters because the real-time WebSocket + // (libwebsockets, incl. under Proton/Wine) can't reach the public domain from inside the + // LAN (hairpin NAT), which surfaced as "error code 5" right after the intro. + private const string LanHost = "192.168.1.136"; - public GetSignalRConnection(IOptions settings) + private readonly PlayFabSettings _settings; + private readonly IHttpContextAccessor _httpContextAccessor; + + public GetSignalRConnection(IOptions settings, IHttpContextAccessor httpContextAccessor) { _settings = settings.Value; + _httpContextAccessor = httpContextAccessor; } public Task ExecuteAsync(FYGetSignalRConnection request) { - // The game client connects to SignalR only over HTTPS + // The game client connects to SignalR only over HTTPS. + var url = _settings.SignalRURL; + + var remote = _httpContextAccessor.HttpContext?.Connection.RemoteIpAddress; + if (remote != null && IsLan(remote)) + { + try + { + url = new UriBuilder(url) { Host = LanHost }.Uri.AbsoluteUri; + } + catch { /* keep the configured URL on any parsing issue */ } + } + return Task.FromResult(new FYGetSignalRConnectionResult { - Url = _settings.SignalRURL, + Url = url, AccessToken = _settings.SignalRAccessToken }); } -} \ No newline at end of file + + private static bool IsLan(IPAddress ip) + { + if (IPAddress.IsLoopback(ip)) return true; + if (ip.IsIPv4MappedToIPv6) ip = ip.MapToIPv4(); + if (ip.AddressFamily != AddressFamily.InterNetwork) return false; + var b = ip.GetAddressBytes(); + return b[0] == 10 + || (b[0] == 172 && b[1] >= 16 && b[1] <= 31) + || (b[0] == 192 && b[1] == 168); + } +}