using System; using System.Collections.Generic; using System.IO; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using ArcaneLibs.Extensions; using LibMatrix.Extensions; using Microsoft.Extensions.Logging; namespace LibMatrix.Services; public class HomeserverResolverService { private readonly MatrixHttpClient _httpClient = new(); private readonly ILogger _logger; private static readonly Dictionary _wellKnownCache = new(); private static readonly Dictionary _wellKnownSemaphores = new(); public HomeserverResolverService(ILogger logger) { _logger = logger; } public async Task ResolveHomeserverFromWellKnown(string homeserver) { var res = await _resolveHomeserverFromWellKnown(homeserver); if (!res.StartsWith("http")) res = "https://" + res; if (res.EndsWith(":443")) res = res[..^4]; return res; } private async Task _resolveHomeserverFromWellKnown(string homeserver) { if (homeserver is null) throw new ArgumentNullException(nameof(homeserver)); var sem = _wellKnownSemaphores.GetOrCreate(homeserver, _ => new SemaphoreSlim(1, 1)); await sem.WaitAsync(); if (_wellKnownCache.TryGetValue(homeserver, out var known)) { sem.Release(); return known; } string? result = null; _logger.LogInformation("Attempting to resolve homeserver: {}", homeserver); result ??= await _tryResolveFromClientWellknown(homeserver); result ??= await _tryResolveFromServerWellknown(homeserver); result ??= await _tryCheckIfDomainHasHomeserver(homeserver); if (result is null) throw new InvalidDataException($"Failed to resolve homeserver for {homeserver}! Is it online and configured correctly?"); //success! _logger.LogInformation("Resolved homeserver: {} -> {}", homeserver, result); _wellKnownCache[homeserver] = result; sem.Release(); return result; } private async Task _tryResolveFromClientWellknown(string homeserver) { if (!homeserver.StartsWith("http")) homeserver = "https://" + homeserver; if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/client")) { var resp = await _httpClient.GetFromJsonAsync($"{homeserver}/.well-known/matrix/client"); var hs = resp.GetProperty("m.homeserver").GetProperty("base_url").GetString(); return hs; } _logger.LogInformation("No client well-known..."); return null; } private async Task _tryResolveFromServerWellknown(string homeserver) { if (!homeserver.StartsWith("http")) homeserver = "https://" + homeserver; if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/server")) { var resp = await _httpClient.GetFromJsonAsync($"{homeserver}/.well-known/matrix/server"); var hs = resp.GetProperty("m.server").GetString(); return hs; } _logger.LogInformation("No server well-known..."); return null; } private async Task _tryCheckIfDomainHasHomeserver(string homeserver) { _logger.LogInformation("Checking if {} hosts a homeserver...", homeserver); if (await _httpClient.CheckSuccessStatus($"{homeserver}/_matrix/client/versions")) return homeserver; _logger.LogInformation("No homeserver on shortname..."); return null; } private async Task _tryCheckIfSubDomainHasHomeserver(string homeserver, string subdomain) { homeserver = homeserver.Replace("https://", $"https://{subdomain}."); return await _tryCheckIfDomainHasHomeserver(homeserver); } }