about summary refs log tree commit diff
path: root/LibMatrix/Services
diff options
context:
space:
mode:
Diffstat (limited to 'LibMatrix/Services')
-rw-r--r--LibMatrix/Services/HomeserverProviderService.cs13
-rw-r--r--LibMatrix/Services/HomeserverResolverService.cs41
-rw-r--r--LibMatrix/Services/ServiceInstaller.cs12
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolverConfiguration.cs49
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs91
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolvers/BaseWellKnownResolver.cs52
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs42
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs38
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolvers/SupportWellKnownResolver.cs44
9 files changed, 343 insertions, 39 deletions
diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs

index a674549..36bc828 100644 --- a/LibMatrix/Services/HomeserverProviderService.cs +++ b/LibMatrix/Services/HomeserverProviderService.cs
@@ -1,6 +1,5 @@ using System.Net.Http.Json; using ArcaneLibs.Collections; -using ArcaneLibs.Extensions; using LibMatrix.Homeservers; using LibMatrix.Responses; using Microsoft.Extensions.Logging; @@ -8,9 +7,9 @@ using Microsoft.Extensions.Logging; namespace LibMatrix.Services; public class HomeserverProviderService(ILogger<HomeserverProviderService> logger, HomeserverResolverService hsResolver) { - private static SemaphoreCache<AuthenticatedHomeserverGeneric> AuthenticatedHomeserverCache = new(); - private static SemaphoreCache<RemoteHomeserver> RemoteHomeserverCache = new(); - private static SemaphoreCache<FederationClient> FederationClientCache = new(); + private static readonly SemaphoreCache<AuthenticatedHomeserverGeneric> AuthenticatedHomeserverCache = new(); + private static readonly SemaphoreCache<RemoteHomeserver> RemoteHomeserverCache = new(); + private static readonly SemaphoreCache<FederationClient> FederationClientCache = new(); public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedWithToken(string homeserver, string accessToken, string? proxy = null, string? impersonatedMxid = null, bool useGeneric = false, bool enableClient = true, bool enableServer = true) { @@ -25,7 +24,7 @@ public class HomeserverProviderService(ILogger<HomeserverProviderService> logger if (!useGeneric) { var clientVersionsTask = rhs.GetClientVersionsAsync(); var serverVersionTask = rhs.FederationClient?.GetServerVersionAsync() ?? Task.FromResult<ServerVersionResponse?>(null)!; - ClientVersionsResponse? clientVersions = new(); + ClientVersionsResponse clientVersions = new(); try { clientVersions = await clientVersionsTask; } @@ -48,6 +47,8 @@ public class HomeserverProviderService(ILogger<HomeserverProviderService> logger else { if (serverVersion is { Server.Name: "Synapse" }) hs = new AuthenticatedHomeserverSynapse(homeserver, wellKnownUris, proxy, accessToken); + else if (serverVersion is { Server.Name: "LibMatrix.HomeserverEmulator"}) + hs = new AuthenticatedHomeserverHSE(homeserver, wellKnownUris, proxy, accessToken); } } catch (Exception e) { @@ -67,7 +68,7 @@ public class HomeserverProviderService(ILogger<HomeserverProviderService> logger }); } - public async Task<RemoteHomeserver> GetRemoteHomeserver(string homeserver, string? proxy = null, bool useCache = true, bool enableServer = true) => + public async Task<RemoteHomeserver> GetRemoteHomeserver(string homeserver, string? proxy = null, bool useCache = true, bool enableServer = false) => useCache ? await RemoteHomeserverCache.GetOrAdd($"{homeserver}{proxy}", async () => { return new RemoteHomeserver(homeserver, await hsResolver.ResolveHomeserverFromWellKnown(homeserver, enableServer: enableServer), proxy); }) diff --git a/LibMatrix/Services/HomeserverResolverService.cs b/LibMatrix/Services/HomeserverResolverService.cs
index dc8e047..53cd2dd 100644 --- a/LibMatrix/Services/HomeserverResolverService.cs +++ b/LibMatrix/Services/HomeserverResolverService.cs
@@ -1,8 +1,4 @@ -using System.Collections.Concurrent; using System.Diagnostics; -using System.Net.Http.Json; -using System.Text.Json; -using System.Text.Json.Nodes; using System.Text.Json.Serialization; using ArcaneLibs.Collections; using ArcaneLibs.Extensions; @@ -13,9 +9,7 @@ using Microsoft.Extensions.Logging.Abstractions; namespace LibMatrix.Services; public class HomeserverResolverService { - private readonly MatrixHttpClient _httpClient = new() { - // Timeout = TimeSpan.FromSeconds(60) // TODO: Re-implement this - }; + private readonly MatrixHttpClient _httpClient = new(); private static readonly SemaphoreCache<WellKnownUris> WellKnownCache = new(); @@ -26,53 +20,36 @@ public class HomeserverResolverService { if (logger is NullLogger<HomeserverResolverService>) { var stackFrame = new StackTrace(true).GetFrame(1); Console.WriteLine( - $"WARN | Null logger provided to HomeserverResolverService!\n{stackFrame.GetMethod().DeclaringType} at {stackFrame.GetFileName()}:{stackFrame.GetFileLineNumber()}"); + $"WARN | Null logger provided to HomeserverResolverService!\n{stackFrame?.GetMethod()?.DeclaringType?.ToString() ?? "null"} at {stackFrame?.GetFileName() ?? "null"}:{stackFrame?.GetFileLineNumber().ToString() ?? "null"}"); } } - // private static SemaphoreSlim _wellKnownSemaphore = new(1, 1); - public async Task<WellKnownUris> ResolveHomeserverFromWellKnown(string homeserver, bool enableClient = true, bool enableServer = true) { ArgumentNullException.ThrowIfNull(homeserver); return await WellKnownCache.GetOrAdd(homeserver, async () => { - // await _wellKnownSemaphore.WaitAsync(); _logger.LogTrace($"Resolving homeserver well-knowns: {homeserver}"); var client = enableClient ? _tryResolveClientEndpoint(homeserver) : null; var server = enableServer ? _tryResolveServerEndpoint(homeserver) : null; var res = new WellKnownUris(); - // try { if (client != null) - res.Client = await client ?? throw new Exception($"Could not resolve client URL for {homeserver}."); - // } - // catch (Exception e) { - // _logger.LogError(e, "Error resolving client well-known for {hs}", homeserver); - // } + res.Client = (await client)?.TrimEnd('/') ?? throw new Exception($"Could not resolve client URL for {homeserver}."); - // try { if (server != null) - res.Server = await server ?? throw new Exception($"Could not resolve server URL for {homeserver}."); - // } - // catch (Exception e) { - // _logger.LogError(e, "Error resolving server well-known for {hs}", homeserver); - // } + res.Server = (await server)?.TrimEnd('/') ?? throw new Exception($"Could not resolve server URL for {homeserver}."); _logger.LogInformation("Resolved well-knowns for {hs}: {json}", homeserver, res.ToJson(indent: false)); - // _wellKnownSemaphore.Release(); return res; }); } - // private async Task<WellKnownUris> InternalResolveHomeserverFromWellKnown(string homeserver) { - - // } - private async Task<string?> _tryResolveClientEndpoint(string homeserver) { ArgumentNullException.ThrowIfNull(homeserver); _logger.LogTrace("Resolving client well-known: {homeserver}", homeserver); ClientWellKnown? clientWellKnown = null; + homeserver = homeserver.TrimEnd('/'); // check if homeserver has a client well-known if (homeserver.StartsWith("https://")) { clientWellKnown = await _httpClient.TryGetFromJsonAsync<ClientWellKnown>($"{homeserver}/.well-known/matrix/client"); @@ -104,6 +81,7 @@ public class HomeserverResolverService { ArgumentNullException.ThrowIfNull(homeserver); _logger.LogTrace($"Resolving server well-known: {homeserver}"); ServerWellKnown? serverWellKnown = null; + homeserver = homeserver.TrimEnd('/'); // check if homeserver has a server well-known if (homeserver.StartsWith("https://")) { serverWellKnown = await _httpClient.TryGetFromJsonAsync<ServerWellKnown>($"{homeserver}/.well-known/matrix/server"); @@ -119,7 +97,7 @@ public class HomeserverResolverService { _logger.LogInformation("Server well-known for {hs}: {json}", homeserver, serverWellKnown?.ToJson() ?? "null"); if (!string.IsNullOrWhiteSpace(serverWellKnown?.Homeserver)) { - var resolved = serverWellKnown.Homeserver; + var resolved = serverWellKnown.Homeserver.TrimEnd('/'); if (resolved.StartsWith("https://") || resolved.StartsWith("http://")) return resolved; if (await MatrixHttpClient.CheckSuccessStatus($"https://{resolved}/_matrix/federation/v1/version")) @@ -130,14 +108,15 @@ public class HomeserverResolverService { } // fallback: most servers host C2S and S2S on the same domain - var clientUrl = await _tryResolveClientEndpoint(homeserver); + var clientUrl = (await _tryResolveClientEndpoint(homeserver)).TrimEnd('/'); if (clientUrl is not null && await MatrixHttpClient.CheckSuccessStatus($"{clientUrl}/_matrix/federation/v1/version")) return clientUrl; _logger.LogInformation("No server well-known for {server}...", homeserver); return null; } - + + [Obsolete("Use authenticated media, available on AuthenticatedHomeserverGeneric", true)] public async Task<string?> ResolveMediaUri(string homeserver, string mxc) { if (homeserver is null) throw new ArgumentNullException(nameof(homeserver)); if (mxc is null) throw new ArgumentNullException(nameof(mxc)); diff --git a/LibMatrix/Services/ServiceInstaller.cs b/LibMatrix/Services/ServiceInstaller.cs
index 8b7e54b..5ffd43a 100644 --- a/LibMatrix/Services/ServiceInstaller.cs +++ b/LibMatrix/Services/ServiceInstaller.cs
@@ -1,5 +1,6 @@ +using LibMatrix.Services.WellKnownResolver; +using LibMatrix.Services.WellKnownResolver.WellKnownResolvers; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; namespace LibMatrix.Services; @@ -9,7 +10,14 @@ public static class ServiceInstaller { services.AddSingleton(config ?? new RoryLibMatrixConfiguration()); //Add services - services.AddSingleton<HomeserverResolverService>(sp => new HomeserverResolverService(sp.GetRequiredService<ILogger<HomeserverResolverService>>())); + services.AddSingleton<ClientWellKnownResolver>(); + services.AddSingleton<ServerWellKnownResolver>(); + services.AddSingleton<SupportWellKnownResolver>(); + if (!services.Any(x => x.ServiceType == typeof(WellKnownResolverConfiguration))) + services.AddSingleton<WellKnownResolverConfiguration>(); + services.AddSingleton<WellKnownResolverService>(); + // Legacy + services.AddSingleton<HomeserverResolverService>(); services.AddSingleton<HomeserverProviderService>(); return services; diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolverConfiguration.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolverConfiguration.cs new file mode 100644
index 0000000..26a4c43 --- /dev/null +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolverConfiguration.cs
@@ -0,0 +1,49 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Services.WellKnownResolver; + +public class WellKnownResolverConfiguration { + /// <summary> + /// Allow transparent downgrades to plaintext HTTP if HTTPS fails + /// Enabling this is unsafe! + /// </summary> + [JsonPropertyName("allow_http")] + public bool AllowHttp { get; set; } = false; + + /// <summary> + /// Use DNS resolution if available, for resolving SRV records + /// </summary> + [JsonPropertyName("allow_dns")] + public bool AllowDns { get; set; } = true; + + /// <summary> + /// Use system resolver(s) if empty + /// </summary> + [JsonPropertyName("dns_servers")] + public List<string> DnsServers { get; set; } = new(); + + /// <summary> + /// Same as AllowDns, but for DNS over HTTPS - useful in browser contexts + /// </summary> + [JsonPropertyName("allow_doh")] + public bool AllowDoh { get; set; } = true; + + /// <summary> + /// Use DNS over HTTPS - useful in browser contexts + /// Disabled if empty + /// </summary> + [JsonPropertyName("doh_servers")] + public List<string> DohServers { get; set; } = new(); + + /// <summary> + /// Whether to allow fallback subdomain lookups + /// </summary> + [JsonPropertyName("allow_fallback_subdomains")] + public bool AllowFallbackSubdomains { get; set; } = true; + + /// <summary> + /// Fallback subdomains to try if the homeserver is not found + /// </summary> + [JsonPropertyName("fallback_subdomains")] + public List<string> FallbackSubdomains { get; set; } = ["matrix", "chat", "im"]; +} \ No newline at end of file diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs new file mode 100644
index 0000000..4c78347 --- /dev/null +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs
@@ -0,0 +1,91 @@ +using System.Diagnostics; +using System.Text.Json.Serialization; +using LibMatrix.Extensions; +using LibMatrix.Services.WellKnownResolver.WellKnownResolvers; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace LibMatrix.Services.WellKnownResolver; + +public class WellKnownResolverService { + private readonly MatrixHttpClient _httpClient = new(); + + private readonly ILogger<WellKnownResolverService> _logger; + private readonly ClientWellKnownResolver _clientWellKnownResolver; + private readonly SupportWellKnownResolver _supportWellKnownResolver; + private readonly ServerWellKnownResolver _serverWellKnownResolver; + private readonly WellKnownResolverConfiguration _configuration; + + public WellKnownResolverService(ILogger<WellKnownResolverService> logger, ClientWellKnownResolver clientWellKnownResolver, SupportWellKnownResolver supportWellKnownResolver, + WellKnownResolverConfiguration configuration, ServerWellKnownResolver serverWellKnownResolver) { + _logger = logger; + _clientWellKnownResolver = clientWellKnownResolver; + _supportWellKnownResolver = supportWellKnownResolver; + _configuration = configuration; + _serverWellKnownResolver = serverWellKnownResolver; + if (logger is NullLogger<WellKnownResolverService>) { + var stackFrame = new StackTrace(true).GetFrame(1); + Console.WriteLine( + $"WARN | Null logger provided to WellKnownResolverService!\n{stackFrame?.GetMethod()?.DeclaringType?.ToString() ?? "null"} at {stackFrame?.GetFileName() ?? "null"}:{stackFrame?.GetFileLineNumber().ToString() ?? "null"}"); + } + } + + public async Task<WellKnownRecords> TryResolveWellKnownRecords(string homeserver, bool includeClient = true, bool includeServer = true, bool includeSupport = true, + WellKnownResolverConfiguration? config = null) { + WellKnownRecords records = new(); + _logger.LogDebug($"Resolving well-knowns for {homeserver}"); + if (includeClient && await _clientWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration) is { } clientResult) { + records.ClientWellKnown = clientResult; + } + + if (includeServer && await _serverWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration) is { } serverResult) { + records.ServerWellKnown = serverResult; + } + + if (includeSupport && await _supportWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration) is { } supportResult) { + records.SupportWellKnown = supportResult; + } + + return records; + } + + public class WellKnownRecords { + public WellKnownResolutionResult<ClientWellKnown?>? ClientWellKnown { get; set; } + public WellKnownResolutionResult<ServerWellKnown?>? ServerWellKnown { get; set; } + public WellKnownResolutionResult<SupportWellKnown?>? SupportWellKnown { get; set; } + } + + public class WellKnownResolutionResult<T> { + public WellKnownSource Source { get; set; } + public string? SourceUri { get; set; } + public T? Content { get; set; } + public List<WellKnownResolutionWarning> Warnings { get; set; } = []; + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum WellKnownSource { + None, + Https, + Dns, + Http, + ManualCheck, + Search + } + + public struct WellKnownResolutionWarning { + public WellKnownResolutionWarningType Type { get; set; } + public string Message { get; set; } + [JsonIgnore] + public Exception? Exception { get; set; } + public string? ExceptionMessage => Exception?.Message; + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum WellKnownResolutionWarningType { + None, + Exception, + InvalidResponse, + Timeout, + SlowResponse + } + } +} \ No newline at end of file diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/BaseWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/BaseWellKnownResolver.cs new file mode 100644
index 0000000..cbe5b0a --- /dev/null +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/BaseWellKnownResolver.cs
@@ -0,0 +1,52 @@ +using System.Diagnostics; +using System.Net.Http.Json; +using ArcaneLibs.Collections; +using LibMatrix.Extensions; + +namespace LibMatrix.Services.WellKnownResolver.WellKnownResolvers; + +public class BaseWellKnownResolver<T> where T : class, new() { + internal static readonly SemaphoreCache<WellKnownResolverService.WellKnownResolutionResult<T>> WellKnownCache = new() { + StoreNulls = false + }; + + internal static readonly MatrixHttpClient HttpClient = new(); + + internal async Task<WellKnownResolverService.WellKnownResolutionResult<T>> TryGetWellKnownFromUrl(string url, + WellKnownResolverService.WellKnownSource source) { + var sw = Stopwatch.StartNew(); + try { + var request = await HttpClient.GetAsync(url); + sw.Stop(); + var result = new WellKnownResolverService.WellKnownResolutionResult<T> { + Content = await request.Content.ReadFromJsonAsync<T>(), + Source = source, + SourceUri = url, + Warnings = [] + }; + + if (sw.ElapsedMilliseconds > 1000) { + // logger.LogWarning($"Support well-known resolution took {sw.ElapsedMilliseconds}ms: {url}"); + result.Warnings.Add(new() { + Type = WellKnownResolverService.WellKnownResolutionWarning.WellKnownResolutionWarningType.SlowResponse, + Message = $"Well-known resolution took {sw.ElapsedMilliseconds}ms" + }); + } + + return result; + } + catch (Exception e) { + return new WellKnownResolverService.WellKnownResolutionResult<T> { + Source = source, + SourceUri = url, + Warnings = [ + new() { + Exception = e, + Type = WellKnownResolverService.WellKnownResolutionWarning.WellKnownResolutionWarningType.Exception, + Message = e.Message + } + ] + }; + } + } +} \ No newline at end of file diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs new file mode 100644
index 0000000..f8de38d --- /dev/null +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs
@@ -0,0 +1,42 @@ +using System.Text.Json.Serialization; +using ArcaneLibs.Collections; +using LibMatrix.Extensions; +using Microsoft.Extensions.Logging; +using WellKnownType = LibMatrix.Services.WellKnownResolver.WellKnownResolvers.ClientWellKnown; +using ResultType = + LibMatrix.Services.WellKnownResolver.WellKnownResolverService.WellKnownResolutionResult<LibMatrix.Services.WellKnownResolver.WellKnownResolvers.ClientWellKnown?>; + +namespace LibMatrix.Services.WellKnownResolver.WellKnownResolvers; + +public class ClientWellKnownResolver(ILogger<ClientWellKnownResolver> logger, WellKnownResolverConfiguration configuration) + : BaseWellKnownResolver<ClientWellKnown> { + private static readonly SemaphoreCache<WellKnownResolverService.WellKnownResolutionResult<ClientWellKnown>> ClientWellKnownCache = new() { + StoreNulls = false + }; + + private static readonly MatrixHttpClient HttpClient = new(); + + public Task<WellKnownResolverService.WellKnownResolutionResult<ClientWellKnown>> TryResolveWellKnown(string homeserver, WellKnownResolverConfiguration? config = null) { + config ??= configuration; + return ClientWellKnownCache.TryGetOrAdd(homeserver, async () => { + logger.LogTrace($"Resolving client well-known: {homeserver}"); + + WellKnownResolverService.WellKnownResolutionResult<ClientWellKnown> result = + await TryGetWellKnownFromUrl($"https://{homeserver}/.well-known/matrix/client", WellKnownResolverService.WellKnownSource.Https); + if (result.Content != null) return result; + + + return result; + }); + } +} + +public class ClientWellKnown { + [JsonPropertyName("m.homeserver")] + public WellKnownHomeserver Homeserver { get; set; } + + public class WellKnownHomeserver { + [JsonPropertyName("base_url")] + public required string BaseUrl { get; set; } + } +} \ No newline at end of file diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs new file mode 100644
index 0000000..a99185c --- /dev/null +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs
@@ -0,0 +1,38 @@ +using System.Text.Json.Serialization; +using ArcaneLibs.Collections; +using LibMatrix.Extensions; +using Microsoft.Extensions.Logging; +using WellKnownType = LibMatrix.Services.WellKnownResolver.WellKnownResolvers.ServerWellKnown; +using ResultType = + LibMatrix.Services.WellKnownResolver.WellKnownResolverService.WellKnownResolutionResult<LibMatrix.Services.WellKnownResolver.WellKnownResolvers.ServerWellKnown?>; + +namespace LibMatrix.Services.WellKnownResolver.WellKnownResolvers; + +public class ServerWellKnownResolver(ILogger<ServerWellKnownResolver> logger, WellKnownResolverConfiguration configuration) + : BaseWellKnownResolver<ServerWellKnown> { + private static readonly SemaphoreCache<WellKnownResolverService.WellKnownResolutionResult<ServerWellKnown>> ClientWellKnownCache = new() { + StoreNulls = false + }; + + private static readonly MatrixHttpClient HttpClient = new(); + + public Task<WellKnownResolverService.WellKnownResolutionResult<ServerWellKnown>> TryResolveWellKnown(string homeserver, WellKnownResolverConfiguration? config = null) { + config ??= configuration; + return ClientWellKnownCache.TryGetOrAdd(homeserver, async () => { + logger.LogTrace($"Resolving client well-known: {homeserver}"); + + WellKnownResolverService.WellKnownResolutionResult<ServerWellKnown> result = + await TryGetWellKnownFromUrl($"https://{homeserver}/.well-known/matrix/server", WellKnownResolverService.WellKnownSource.Https); + if (result.Content != null) return result; + + + return result; + }); + } +} + + +public class ServerWellKnown { + [JsonPropertyName("m.server")] + public string Homeserver { get; set; } +} \ No newline at end of file diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/SupportWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/SupportWellKnownResolver.cs new file mode 100644
index 0000000..99313db --- /dev/null +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/SupportWellKnownResolver.cs
@@ -0,0 +1,44 @@ +using System.Diagnostics; +using System.Net.Http.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Logging; +using WellKnownType = LibMatrix.Services.WellKnownResolver.WellKnownResolvers.SupportWellKnown; +using ResultType = LibMatrix.Services.WellKnownResolver.WellKnownResolverService.WellKnownResolutionResult< + LibMatrix.Services.WellKnownResolver.WellKnownResolvers.SupportWellKnown? +>; + +namespace LibMatrix.Services.WellKnownResolver.WellKnownResolvers; + +public class SupportWellKnownResolver(ILogger<SupportWellKnownResolver> logger, WellKnownResolverConfiguration configuration) : BaseWellKnownResolver<WellKnownType> { + public Task<ResultType> TryResolveWellKnown(string homeserver, WellKnownResolverConfiguration? config = null) { + config ??= configuration; + return WellKnownCache.TryGetOrAdd(homeserver, async () => { + logger.LogTrace($"Resolving support well-known: {homeserver}"); + + ResultType result = await TryGetWellKnownFromUrl($"https://{homeserver}/.well-known/matrix/support", WellKnownResolverService.WellKnownSource.Https); + if (result.Content != null) + return result; + + return null; + }); + } +} + +public class SupportWellKnown { + [JsonPropertyName("contacts")] + public List<WellKnownContact>? Contacts { get; set; } + + [JsonPropertyName("support_page")] + public Uri? SupportPage { get; set; } + + public class WellKnownContact { + [JsonPropertyName("email_address")] + public string? EmailAddress { get; set; } + + [JsonPropertyName("matrix_id")] + public string? MatrixId { get; set; } + + [JsonPropertyName("role")] + public required string Role { get; set; } + } +} \ No newline at end of file