Add .well-known HubConnectionConfig
This commit is contained in:
@@ -177,7 +177,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
|
|||||||
|
|
||||||
if (token.IsCancellationRequested) break;
|
if (token.IsCancellationRequested) break;
|
||||||
|
|
||||||
_mareHub = _hubFactory.GetOrCreate(token);
|
_mareHub = await _hubFactory.GetOrCreate(token);
|
||||||
InitializeApiHooks();
|
InitializeApiHooks();
|
||||||
|
|
||||||
await _mareHub.StartAsync(token).ConfigureAwait(false);
|
await _mareHub.StartAsync(token).ConfigureAwait(false);
|
||||||
|
|||||||
47
MareSynchronos/WebAPI/SignalR/HubConnectionConfig.cs
Normal file
47
MareSynchronos/WebAPI/SignalR/HubConnectionConfig.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using Microsoft.AspNetCore.Http.Connections;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace MareSynchronos.WebAPI.SignalR;
|
||||||
|
|
||||||
|
public record HubConnectionConfig
|
||||||
|
{
|
||||||
|
[JsonPropertyName("hub_url")]
|
||||||
|
public string HubUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
private readonly bool? _skipNegotiation;
|
||||||
|
|
||||||
|
[JsonPropertyName("skip_negotiation")]
|
||||||
|
public bool SkipNegotiation
|
||||||
|
{
|
||||||
|
get => _skipNegotiation ?? true;
|
||||||
|
init => _skipNegotiation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("transports")]
|
||||||
|
public string[]? Transports { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public HttpTransportType TransportType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Transports == null || Transports.Length == 0)
|
||||||
|
return HttpTransportType.WebSockets;
|
||||||
|
|
||||||
|
HttpTransportType result = HttpTransportType.None;
|
||||||
|
|
||||||
|
foreach (var transport in Transports)
|
||||||
|
{
|
||||||
|
result |= transport.ToLowerInvariant() switch
|
||||||
|
{
|
||||||
|
"websockets" => HttpTransportType.WebSockets,
|
||||||
|
"serversentevents" => HttpTransportType.ServerSentEvents,
|
||||||
|
"longpolling" => HttpTransportType.LongPolling,
|
||||||
|
_ => HttpTransportType.None
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ using Microsoft.AspNetCore.Http.Connections;
|
|||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace MareSynchronos.WebAPI.SignalR;
|
namespace MareSynchronos.WebAPI.SignalR;
|
||||||
|
|
||||||
@@ -17,6 +20,8 @@ public class HubFactory : MediatorSubscriberBase
|
|||||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||||
private readonly TokenProvider _tokenProvider;
|
private readonly TokenProvider _tokenProvider;
|
||||||
private HubConnection? _instance;
|
private HubConnection? _instance;
|
||||||
|
private string _cachedConfigFor = string.Empty;
|
||||||
|
private HubConnectionConfig? _cachedConfig;
|
||||||
private bool _isDisposed = false;
|
private bool _isDisposed = false;
|
||||||
|
|
||||||
public HubFactory(ILogger<HubFactory> logger, MareMediator mediator,
|
public HubFactory(ILogger<HubFactory> logger, MareMediator mediator,
|
||||||
@@ -48,23 +53,110 @@ public class HubFactory : MediatorSubscriberBase
|
|||||||
Logger.LogDebug("Current HubConnection disposed");
|
Logger.LogDebug("Current HubConnection disposed");
|
||||||
}
|
}
|
||||||
|
|
||||||
public HubConnection GetOrCreate(CancellationToken ct)
|
public async Task<HubConnection> GetOrCreate(CancellationToken ct)
|
||||||
{
|
{
|
||||||
if (!_isDisposed && _instance != null) return _instance;
|
if (!_isDisposed && _instance != null) return _instance;
|
||||||
|
|
||||||
return BuildHubConnection(ct);
|
_cachedConfig = await ResolveHubConfig();
|
||||||
|
_cachedConfigFor = _serverConfigurationManager.CurrentApiUrl;
|
||||||
|
|
||||||
|
return BuildHubConnection(_cachedConfig, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
private HubConnection BuildHubConnection(CancellationToken ct)
|
public async Task<HubConnectionConfig> ResolveHubConfig()
|
||||||
|
{
|
||||||
|
var uri = new Uri(_serverConfigurationManager.CurrentApiUrl);
|
||||||
|
|
||||||
|
var httpScheme = uri.Scheme.ToLowerInvariant() switch
|
||||||
|
{
|
||||||
|
"ws" => "http",
|
||||||
|
"wss" => "https",
|
||||||
|
_ => uri.Scheme
|
||||||
|
};
|
||||||
|
|
||||||
|
var wellKnownUrl = $"{httpScheme}://{uri.Host}/.well-known/loporrit/client";
|
||||||
|
|
||||||
|
using var httpClient = new HttpClient(
|
||||||
|
new HttpClientHandler
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = true,
|
||||||
|
MaxAutomaticRedirections = 5
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
var ver = Assembly.GetExecutingAssembly().GetName().Version;
|
||||||
|
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("MareSynchronos", ver!.Major + "." + ver!.Minor + "." + ver!.Build));
|
||||||
|
|
||||||
|
HubConnectionConfig defaultConfig;
|
||||||
|
|
||||||
|
if (_cachedConfig != null && _serverConfigurationManager.CurrentApiUrl == _cachedConfigFor)
|
||||||
|
{
|
||||||
|
defaultConfig = _cachedConfig;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
defaultConfig = new HubConnectionConfig
|
||||||
|
{
|
||||||
|
HubUrl = uri.AbsoluteUri.TrimEnd('/') + IMareHub.Path,
|
||||||
|
Transports = []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Make a GET request to the loporrit endpoint
|
||||||
|
var response = await httpClient.GetAsync(wellKnownUrl).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
return defaultConfig;
|
||||||
|
|
||||||
|
var contentType = response.Content.Headers.ContentType?.MediaType;
|
||||||
|
|
||||||
|
if (contentType == null || contentType != "application/json")
|
||||||
|
return defaultConfig;
|
||||||
|
|
||||||
|
var jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
var config = JsonSerializer.Deserialize<HubConnectionConfig>(
|
||||||
|
jsonResponse,
|
||||||
|
new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
AllowTrailingCommas = true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (config == null)
|
||||||
|
return defaultConfig;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(config.HubUrl))
|
||||||
|
config.HubUrl ??= defaultConfig.HubUrl;
|
||||||
|
|
||||||
|
config.Transports ??= [];
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
Logger.LogWarning(ex, "HTTP request failed for .well-known");
|
||||||
|
return defaultConfig;
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
Logger.LogWarning(ex, "Invalid JSON in .well-known response");
|
||||||
|
return defaultConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HubConnection BuildHubConnection(HubConnectionConfig hubConfig, CancellationToken ct)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Building new HubConnection");
|
Logger.LogDebug("Building new HubConnection");
|
||||||
|
|
||||||
_instance = new HubConnectionBuilder()
|
_instance = new HubConnectionBuilder()
|
||||||
.WithUrl(_serverConfigurationManager.CurrentApiUrl + IMareHub.Path, options =>
|
.WithUrl(hubConfig.HubUrl, options =>
|
||||||
{
|
{
|
||||||
|
var transports = hubConfig.TransportType;
|
||||||
options.AccessTokenProvider = () => _tokenProvider.GetOrUpdateToken(ct);
|
options.AccessTokenProvider = () => _tokenProvider.GetOrUpdateToken(ct);
|
||||||
options.SkipNegotiation = true;
|
options.SkipNegotiation = hubConfig.SkipNegotiation && (transports == HttpTransportType.WebSockets);
|
||||||
options.Transports = HttpTransportType.WebSockets;
|
options.Transports = transports;
|
||||||
})
|
})
|
||||||
.AddMessagePackProtocol(opt =>
|
.AddMessagePackProtocol(opt =>
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user