Update 0.1.2 - AutoDetect Debug before release
This commit is contained in:
170
MareSynchronos/Services/AutoDetect/DiscoveryConfigProvider.cs
Normal file
170
MareSynchronos/Services/AutoDetect/DiscoveryConfigProvider.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Services.ServerConfiguration;
|
||||
using MareSynchronos.WebAPI.SignalR;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MareSynchronos.Services.AutoDetect;
|
||||
|
||||
public class DiscoveryConfigProvider
|
||||
{
|
||||
private readonly ILogger<DiscoveryConfigProvider> _logger;
|
||||
private readonly ServerConfigurationManager _serverManager;
|
||||
private readonly TokenProvider _tokenProvider;
|
||||
|
||||
private WellKnownRoot? _config;
|
||||
private DateTimeOffset _lastLoad = DateTimeOffset.MinValue;
|
||||
|
||||
public DiscoveryConfigProvider(ILogger<DiscoveryConfigProvider> logger, ServerConfigurationManager serverManager, TokenProvider tokenProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_serverManager = serverManager;
|
||||
_tokenProvider = tokenProvider;
|
||||
}
|
||||
|
||||
public bool HasConfig => _config != null;
|
||||
public bool NearbyEnabled => _config?.NearbyDiscovery?.Enabled ?? false;
|
||||
public byte[]? Salt => _config?.NearbyDiscovery?.SaltBytes;
|
||||
public DateTimeOffset? SaltExpiresAt => _config?.NearbyDiscovery?.SaltExpiresAt;
|
||||
public int RefreshSec => _config?.NearbyDiscovery?.RefreshSec ?? 300;
|
||||
public int MinQueryIntervalMs => _config?.NearbyDiscovery?.Policies?.MinQueryIntervalMs ?? 2000;
|
||||
public int MaxQueryBatch => _config?.NearbyDiscovery?.Policies?.MaxQueryBatch ?? 100;
|
||||
public string? PublishEndpoint => _config?.NearbyDiscovery?.Endpoints?.Publish;
|
||||
public string? QueryEndpoint => _config?.NearbyDiscovery?.Endpoints?.Query;
|
||||
public string? RequestEndpoint => _config?.NearbyDiscovery?.Endpoints?.Request;
|
||||
|
||||
public bool TryLoadFromStapled()
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = _tokenProvider.GetStapledWellKnown(_serverManager.CurrentApiUrl);
|
||||
if (string.IsNullOrEmpty(json)) return false;
|
||||
|
||||
var root = JsonSerializer.Deserialize<WellKnownRoot>(json!);
|
||||
if (root == null) return false;
|
||||
|
||||
root.NearbyDiscovery?.Hydrate();
|
||||
_config = root;
|
||||
_lastLoad = DateTimeOffset.UtcNow;
|
||||
_logger.LogDebug("Loaded Nearby well-known (stapled), enabled={enabled}", NearbyEnabled);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to parse stapled well-known");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> TryFetchFromServerAsync(CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var baseUrl = _serverManager.CurrentApiUrl
|
||||
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase);
|
||||
// Try likely candidates based on nginx config
|
||||
string[] candidates =
|
||||
[
|
||||
"/.well-known/Umbra/client", // matches provided nginx
|
||||
"/.well-known/umbra", // lowercase variant
|
||||
];
|
||||
|
||||
using var http = new HttpClient();
|
||||
try
|
||||
{
|
||||
var ver = Assembly.GetExecutingAssembly().GetName().Version!;
|
||||
http.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("MareSynchronos", $"{ver.Major}.{ver.Minor}.{ver.Build}"));
|
||||
}
|
||||
catch { }
|
||||
|
||||
foreach (var path in candidates)
|
||||
{
|
||||
try
|
||||
{
|
||||
var uri = new Uri(new Uri(baseUrl), path);
|
||||
var json = await http.GetStringAsync(uri, ct).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(json)) continue;
|
||||
|
||||
var root = JsonSerializer.Deserialize<WellKnownRoot>(json);
|
||||
if (root == null) continue;
|
||||
|
||||
root.NearbyDiscovery?.Hydrate();
|
||||
_config = root;
|
||||
_lastLoad = DateTimeOffset.UtcNow;
|
||||
_logger.LogInformation("Loaded Nearby well-known (http {path}), enabled={enabled}", path, NearbyEnabled);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "Nearby well-known fetch failed for {path}", path);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Nearby well-known not found via HTTP candidates");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to fetch Nearby well-known via HTTP");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsExpired()
|
||||
{
|
||||
if (_config?.NearbyDiscovery?.SaltExpiresAt == null) return false;
|
||||
return DateTimeOffset.UtcNow > _config.NearbyDiscovery.SaltExpiresAt;
|
||||
}
|
||||
|
||||
// DTOs for well-known JSON
|
||||
private sealed class WellKnownRoot
|
||||
{
|
||||
[JsonPropertyName("features")] public Features? Features { get; set; }
|
||||
[JsonPropertyName("nearby_discovery")] public Nearby? NearbyDiscovery { get; set; }
|
||||
}
|
||||
|
||||
private sealed class Features
|
||||
{
|
||||
[JsonPropertyName("nearby_discovery")] public bool NearbyDiscovery { get; set; }
|
||||
}
|
||||
|
||||
private sealed class Nearby
|
||||
{
|
||||
[JsonPropertyName("enabled")] public bool Enabled { get; set; }
|
||||
[JsonPropertyName("hash_algo")] public string? HashAlgo { get; set; }
|
||||
[JsonPropertyName("salt_b64")] public string? SaltB64 { get; set; }
|
||||
[JsonPropertyName("salt_expires_at")] public string? SaltExpiresAtRaw { get; set; }
|
||||
[JsonPropertyName("refresh_sec")] public int RefreshSec { get; set; } = 300;
|
||||
[JsonPropertyName("endpoints")] public Endpoints? Endpoints { get; set; }
|
||||
[JsonPropertyName("policies")] public Policies? Policies { get; set; }
|
||||
|
||||
[JsonIgnore] public byte[]? SaltBytes { get; private set; }
|
||||
[JsonIgnore] public DateTimeOffset? SaltExpiresAt { get; private set; }
|
||||
|
||||
public void Hydrate()
|
||||
{
|
||||
try { SaltBytes = string.IsNullOrEmpty(SaltB64) ? null : Convert.FromBase64String(SaltB64!); } catch { SaltBytes = null; }
|
||||
if (DateTimeOffset.TryParse(SaltExpiresAtRaw, out var dto)) SaltExpiresAt = dto;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Endpoints
|
||||
{
|
||||
[JsonPropertyName("publish")] public string? Publish { get; set; }
|
||||
[JsonPropertyName("query")] public string? Query { get; set; }
|
||||
[JsonPropertyName("request")] public string? Request { get; set; }
|
||||
}
|
||||
|
||||
private sealed class Policies
|
||||
{
|
||||
[JsonPropertyName("max_query_batch")] public int MaxQueryBatch { get; set; } = 100;
|
||||
[JsonPropertyName("min_query_interval_ms")] public int MinQueryIntervalMs { get; set; } = 2000;
|
||||
[JsonPropertyName("rate_limit_per_min")] public int RateLimitPerMin { get; set; } = 30;
|
||||
[JsonPropertyName("token_ttl_sec")] public int TokenTtlSec { get; set; } = 120;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user