fix(signalr): return LAN SignalR URL to local clients
Build & Deploy / build (push) Successful in 34s
Build & Deploy / build (push) Successful in 34s
GetSignalRConnection now serves the SignalR URL on 192.168.1.136 to clients whose remote IP is on the LAN, and keeps the public domain for external clients. The real-time WebSocket (libwebsockets, incl. under Proton/Wine) couldn't reach the public domain from inside the LAN (hairpin NAT) — it failed right after the intro as "error code 5". External friends are unaffected (still get the domain). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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<FYGetSignalRConnection, FYGetSignalRConnectionResult>
|
||||
{
|
||||
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<PlayFabSettings> settings)
|
||||
private readonly PlayFabSettings _settings;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public GetSignalRConnection(IOptions<PlayFabSettings> settings, IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_settings = settings.Value;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public Task<FYGetSignalRConnectionResult> 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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user