diff options
author | TheArcaneBrony <myrainbowdash949@gmail.com> | 2023-10-26 13:09:05 +0000 |
---|---|---|
committer | TheArcaneBrony <myrainbowdash949@gmail.com> | 2023-10-26 13:09:05 +0000 |
commit | b75135d8cdb702423d693558ffaec3f025264b98 (patch) | |
tree | 1e7c0d746ac387a0d6b648d4e452169db17debc6 | |
parent | Minor cleanup (diff) | |
download | LibMatrix-b75135d8cdb702423d693558ffaec3f025264b98.tar.xz |
split client and server http client for homeservers
-rw-r--r-- | LibMatrix/Helpers/SyncHelper.cs | 2 | ||||
-rw-r--r-- | LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs | 36 | ||||
-rw-r--r-- | LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs | 2 | ||||
-rw-r--r-- | LibMatrix/Homeservers/RemoteHomeServer.cs | 48 | ||||
-rw-r--r-- | LibMatrix/Responses/LoginResponse.cs | 4 | ||||
-rw-r--r-- | LibMatrix/RoomTypes/GenericRoom.cs | 35 | ||||
-rw-r--r-- | LibMatrix/Services/HomeserverProviderService.cs | 31 | ||||
-rw-r--r-- | LibMatrix/Services/HomeserverResolverService.cs | 63 | ||||
-rw-r--r-- | Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs | 4 | ||||
-rw-r--r-- | Tests/LibMatrix.Tests/Tests/ResolverTest.cs | 2 |
10 files changed, 130 insertions, 97 deletions
diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs index c6c5378..fb7fad2 100644 --- a/LibMatrix/Helpers/SyncHelper.cs +++ b/LibMatrix/Helpers/SyncHelper.cs @@ -27,7 +27,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg // Console.WriteLine("Calling: " + url); logger?.LogInformation("SyncHelper: Calling: {}", url); try { - return await homeserver?._httpClient?.GetFromJsonAsync<SyncResponse>(url, cancellationToken: cancellationToken ?? CancellationToken.None)!; + return await homeserver?.ClientHttpClient?.GetFromJsonAsync<SyncResponse>(url, cancellationToken: cancellationToken ?? CancellationToken.None)!; } catch (TaskCanceledException) { Console.WriteLine("Sync cancelled!"); diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs index e5e4274..c3684a1 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs @@ -13,19 +13,29 @@ using LibMatrix.Services; namespace LibMatrix.Homeservers; -public class AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) : RemoteHomeServer(baseUrl) { +public class AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) : RemoteHomeserver(baseUrl) { public static async Task<T> Create<T>(string baseUrl, string accessToken) where T : AuthenticatedHomeserverGeneric { var instance = Activator.CreateInstance(typeof(T), baseUrl, accessToken) as T ?? throw new InvalidOperationException($"Failed to create instance of {typeof(T).Name}"); - instance._httpClient = new() { - BaseAddress = new Uri(await new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl) + var urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl); + + instance.ClientHttpClient = new() { + BaseAddress = new Uri(urls.client ?? throw new InvalidOperationException("Failed to resolve homeserver")), Timeout = TimeSpan.FromMinutes(15), DefaultRequestHeaders = { Authorization = new AuthenticationHeaderValue("Bearer", accessToken) } }; - instance.WhoAmI = await instance._httpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami"); + instance.ServerHttpClient = new() { + BaseAddress = new Uri(urls.server + ?? throw new InvalidOperationException("Failed to resolve homeserver")), + Timeout = TimeSpan.FromMinutes(15), + DefaultRequestHeaders = { + Authorization = new AuthenticationHeaderValue("Bearer", accessToken) + } + }; + instance.WhoAmI = await instance.ClientHttpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami"); return instance; } @@ -59,7 +69,7 @@ public class AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) } public virtual async Task<List<GenericRoom>> GetJoinedRooms() { - var roomQuery = await _httpClient.GetAsync("/_matrix/client/v3/joined_rooms"); + var roomQuery = await ClientHttpClient.GetAsync("/_matrix/client/v3/joined_rooms"); var roomsJson = await roomQuery.Content.ReadFromJsonAsync<JsonElement>(); var rooms = roomsJson.GetProperty("joined_rooms").EnumerateArray().Select(room => GetRoom(room.GetString()!)).ToList(); @@ -70,7 +80,7 @@ public class AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) } public virtual async Task<string> UploadFile(string fileName, Stream fileStream, string contentType = "application/octet-stream") { - var res = await _httpClient.PostAsync($"/_matrix/media/v3/upload?filename={fileName}", new StreamContent(fileStream)); + var res = await ClientHttpClient.PostAsync($"/_matrix/media/v3/upload?filename={fileName}", new StreamContent(fileStream)); if (!res.IsSuccessStatusCode) { Console.WriteLine($"Failed to upload file: {await res.Content.ReadAsStringAsync()}"); throw new InvalidDataException($"Failed to upload file: {await res.Content.ReadAsStringAsync()}"); @@ -99,7 +109,7 @@ public class AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) } creationEvent.CreationContent["creator"] = WhoAmI.UserId; - var res = await _httpClient.PostAsJsonAsync("/_matrix/client/v3/createRoom", creationEvent, new JsonSerializerOptions { + var res = await ClientHttpClient.PostAsJsonAsync("/_matrix/client/v3/createRoom", creationEvent, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); if (!res.IsSuccessStatusCode) { @@ -116,7 +126,7 @@ public class AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) } public virtual async Task Logout() { - var res = await _httpClient.PostAsync("/_matrix/client/v3/logout", null); + var res = await ClientHttpClient.PostAsync("/_matrix/client/v3/logout", null); if (!res.IsSuccessStatusCode) { Console.WriteLine($"Failed to logout: {await res.Content.ReadAsStringAsync()}"); throw new InvalidDataException($"Failed to logout: {await res.Content.ReadAsStringAsync()}"); @@ -153,11 +163,11 @@ public class AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) // } // // return await res.Content.ReadFromJsonAsync<T>(); - return await _httpClient.GetFromJsonAsync<T>($"/_matrix/client/v3/user/{WhoAmI.UserId}/account_data/{key}"); + return await ClientHttpClient.GetFromJsonAsync<T>($"/_matrix/client/v3/user/{WhoAmI.UserId}/account_data/{key}"); } public virtual async Task SetAccountDataAsync(string key, object data) { - var res = await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/user/{WhoAmI.UserId}/account_data/{key}", data); + var res = await ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/user/{WhoAmI.UserId}/account_data/{key}", data); if (!res.IsSuccessStatusCode) { Console.WriteLine($"Failed to set account data: {await res.Content.ReadAsStringAsync()}"); throw new InvalidDataException($"Failed to set account data: {await res.Content.ReadAsStringAsync()}"); @@ -169,7 +179,7 @@ public class AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) public string? ResolveMediaUri(string? mxcUri) { if (mxcUri is null) return null; if (mxcUri.StartsWith("https://")) return mxcUri; - return $"{_httpClient.BaseAddress}/_matrix/media/v3/download/{mxcUri.Replace("mxc://", "")}".Replace("//_matrix", "/_matrix"); + return $"{ClientHttpClient.BaseAddress}/_matrix/media/v3/download/{mxcUri.Replace("mxc://", "")}".Replace("//_matrix", "/_matrix"); } public async Task UpdateProfileAsync(UserProfileResponse? newProfile, bool preserveCustomRoomProfile = true) { @@ -217,14 +227,14 @@ public class AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) } if (oldProfile.DisplayName != newProfile.DisplayName) { - await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/profile/{WhoAmI.UserId}/displayname", new { displayname = newProfile.DisplayName }); + await ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/profile/{WhoAmI.UserId}/displayname", new { displayname = newProfile.DisplayName }); } else { Console.WriteLine($"Not updating display name because {oldProfile.DisplayName} == {newProfile.DisplayName}"); } if (oldProfile.AvatarUrl != newProfile.AvatarUrl) { - await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/profile/{WhoAmI.UserId}/avatar_url", new { avatar_url = newProfile.AvatarUrl }); + await ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/profile/{WhoAmI.UserId}/avatar_url", new { avatar_url = newProfile.AvatarUrl }); } else { Console.WriteLine($"Not updating avatar URL because {newProfile.AvatarUrl} == {newProfile.AvatarUrl}"); diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs index 6d60dd7..0910cbe 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs @@ -23,7 +23,7 @@ public class AuthenticatedHomeserverSynapse : AuthenticatedHomeserverGeneric { Console.WriteLine($"--- ADMIN Querying Room List with URL: {url} - Already have {i} items... ---"); - res = await _authenticatedHomeserver._httpClient.GetFromJsonAsync<AdminRoomListingResult>(url); + res = await _authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<AdminRoomListingResult>(url); totalRooms ??= res?.TotalRooms; Console.WriteLine(res.ToJson(false)); foreach (var room in res.Rooms) { diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs index a8d0326..0757f6e 100644 --- a/LibMatrix/Homeservers/RemoteHomeServer.cs +++ b/LibMatrix/Homeservers/RemoteHomeServer.cs @@ -9,19 +9,25 @@ using LibMatrix.Services; namespace LibMatrix.Homeservers; -public class RemoteHomeServer(string baseUrl) { - public static async Task<RemoteHomeServer> Create(string baseUrl) => - new(baseUrl) { - _httpClient = new() { - BaseAddress = new Uri(await new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl) - ?? throw new InvalidOperationException("Failed to resolve homeserver")), +public class RemoteHomeserver(string baseUrl) { + public static async Task<RemoteHomeserver> Create(string baseUrl) { + var urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl); + return new RemoteHomeserver(baseUrl) { + ClientHttpClient = new() { + BaseAddress = new Uri(urls.client ?? throw new InvalidOperationException("Failed to resolve homeserver")), + Timeout = TimeSpan.FromSeconds(120) + }, + ServerHttpClient = new() { + BaseAddress = new Uri(urls.server ?? throw new InvalidOperationException("Failed to resolve homeserver")), Timeout = TimeSpan.FromSeconds(120) } }; + } private Dictionary<string, object> _profileCache { get; set; } = new(); public string BaseUrl { get; } = baseUrl; - public MatrixHttpClient _httpClient { get; set; } + public MatrixHttpClient ClientHttpClient { get; set; } + public MatrixHttpClient ServerHttpClient { get; set; } public async Task<UserProfileResponse> GetProfileAsync(string mxid) { if (mxid is null) throw new ArgumentNullException(nameof(mxid)); @@ -32,7 +38,7 @@ public class RemoteHomeServer(string baseUrl) { _profileCache[mxid] = new SemaphoreSlim(1); - var resp = await _httpClient.GetAsync($"/_matrix/client/v3/profile/{mxid}"); + var resp = await ClientHttpClient.GetAsync($"/_matrix/client/v3/profile/{mxid}"); var data = await resp.Content.ReadFromJsonAsync<UserProfileResponse>(); if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data); _profileCache[mxid] = data; @@ -41,14 +47,14 @@ public class RemoteHomeServer(string baseUrl) { } public async Task<ClientVersionsResponse> GetClientVersionsAsync() { - var resp = await _httpClient.GetAsync($"/_matrix/client/versions"); + var resp = await ClientHttpClient.GetAsync($"/_matrix/client/versions"); var data = await resp.Content.ReadFromJsonAsync<ClientVersionsResponse>(); if (!resp.IsSuccessStatusCode) Console.WriteLine("ClientVersions: " + data); return data; } public async Task<AliasResult> ResolveRoomAliasAsync(string alias) { - var resp = await _httpClient.GetAsync($"/_matrix/client/v3/directory/room/{alias.Replace("#", "%23")}"); + var resp = await ClientHttpClient.GetAsync($"/_matrix/client/v3/directory/room/{alias.Replace("#", "%23")}"); var data = await resp.Content.ReadFromJsonAsync<AliasResult>(); var text = await resp.Content.ReadAsStringAsync(); if (!resp.IsSuccessStatusCode) Console.WriteLine("ResolveAlias: " + data.ToJson()); @@ -58,7 +64,7 @@ public class RemoteHomeServer(string baseUrl) { #region Authentication public async Task<LoginResponse> LoginAsync(string username, string password, string? deviceName = null) { - var resp = await _httpClient.PostAsJsonAsync("/_matrix/client/r0/login", new { + var resp = await ClientHttpClient.PostAsJsonAsync("/_matrix/client/r0/login", new { type = "m.login.password", identifier = new { type = "m.id.user", @@ -73,7 +79,7 @@ public class RemoteHomeServer(string baseUrl) { } public async Task<LoginResponse> RegisterAsync(string username, string password, string? deviceName = null) { - var resp = await _httpClient.PostAsJsonAsync("/_matrix/client/r0/register", new { + var resp = await ClientHttpClient.PostAsJsonAsync("/_matrix/client/r0/register", new { kind = "user", auth = new { type = "m.login.dummy" @@ -90,6 +96,24 @@ public class RemoteHomeServer(string baseUrl) { } #endregion + + public async Task<ServerVersionResponse> GetServerVersionAsync() { + return await ServerHttpClient.GetFromJsonAsync<ServerVersionResponse>("/_matrix/federation/v1/version"); + } +} + +public class ServerVersionResponse { + + [JsonPropertyName("server")] + public ServerInfo Server { get; set; } + + public class ServerInfo { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("version")] + public string Version { get; set; } + } } public class AliasResult { diff --git a/LibMatrix/Responses/LoginResponse.cs b/LibMatrix/Responses/LoginResponse.cs index 07b1601..a9ef3be 100644 --- a/LibMatrix/Responses/LoginResponse.cs +++ b/LibMatrix/Responses/LoginResponse.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; using LibMatrix.Homeservers; using LibMatrix.Services; @@ -23,7 +24,8 @@ public class LoginResponse { public string UserId { get; set; } = null!; public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedHomeserver(string? proxy = null) { - return await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverGeneric>(proxy ?? await new HomeserverResolverService().ResolveHomeserverFromWellKnown(Homeserver), AccessToken); + var urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(Homeserver); + return await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverGeneric>(proxy ?? urls.client, AccessToken); } } public class LoginRequest { diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs index 1398f14..96bcefd 100644 --- a/LibMatrix/RoomTypes/GenericRoom.cs +++ b/LibMatrix/RoomTypes/GenericRoom.cs @@ -18,7 +18,7 @@ public class GenericRoom { if (string.IsNullOrWhiteSpace(roomId)) throw new ArgumentException("Room ID cannot be null or whitespace", nameof(roomId)); Homeserver = homeserver; - _httpClient = homeserver._httpClient; + _httpClient = homeserver.ClientHttpClient; RoomId = roomId; if (GetType() != typeof(SpaceRoom)) AsSpace = new SpaceRoom(homeserver, RoomId); @@ -83,11 +83,7 @@ public class GenericRoom { return res ?? new MessagesResponse(); } - // TODO: should we even error handle here? - public async Task<string?> GetNameAsync() { - var res = await GetStateAsync<RoomNameEventContent>("m.room.name"); - return res?.Name; - } + public async Task<string?> GetNameAsync() => (await GetStateAsync<RoomNameEventContent>("m.room.name"))?.Name; public async Task<RoomIdResponse> JoinAsync(string[]? homeservers = null, string? reason = null) { var join_url = $"/_matrix/client/v3/join/{HttpUtility.UrlEncode(RoomId)}"; @@ -100,7 +96,7 @@ public class GenericRoom { return await res.Content.ReadFromJsonAsync<RoomIdResponse>() ?? throw new Exception("Failed to join room?"); } - // TODO: rewrite (members endpoint?) + public async IAsyncEnumerable<StateEventResponse> GetMembersAsync(bool joinedOnly = true) { // var res = GetFullStateAsync(); // await foreach (var member in res) { @@ -108,7 +104,7 @@ public class GenericRoom { // if (joinedOnly && (member.TypedContent as RoomMemberEventContent)?.Membership is not "join") continue; // yield return member; // } - var res = await _httpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members?limit=2"); + var res = await _httpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members"); var resText = await res.Content.ReadAsStringAsync(); var result = await JsonSerializer.DeserializeAsync<ChunkedStateEventResponse>(await res.Content.ReadAsStreamAsync()); foreach (var resp in result.Chunk) { @@ -157,6 +153,29 @@ public class GenericRoom { public async Task<RoomPowerLevelEventContent?> GetPowerLevelsAsync() => await GetStateAsync<RoomPowerLevelEventContent>("m.room.power_levels"); + public async Task<string> GetNameOrFallbackAsync() { + try { + return await GetNameAsync(); + } + catch { + try { + var members = GetMembersAsync(); + var memberList = new List<string>(); + int memberCount = 0; + await foreach (var member in members) + memberList.Add((member.TypedContent is RoomMemberEventContent memberEvent ? memberEvent.DisplayName : "") ?? ""); + memberCount = memberList.Count; + memberList.RemoveAll(string.IsNullOrWhiteSpace); + if (memberList.Count >= 3) + return string.Join(", ", memberList.Take(2)) + " and " + (memberCount - 2) + " others."; + return string.Join(", ", memberList); + } + catch { + return RoomId; + } + } + } + #endregion public async Task ForgetAsync() => diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs index c7fa5c3..a43f518 100644 --- a/LibMatrix/Services/HomeserverProviderService.cs +++ b/LibMatrix/Services/HomeserverProviderService.cs @@ -16,23 +16,26 @@ public class HomeserverProviderService { } private static Dictionary<string, SemaphoreSlim> _authenticatedHomeserverSemaphore = new(); - private static Dictionary<string, AuthenticatedHomeserverGeneric> _authenticatedHomeServerCache = new(); + private static Dictionary<string, AuthenticatedHomeserverGeneric> _authenticatedHomeserverCache = new(); private static Dictionary<string, SemaphoreSlim> _remoteHomeserverSemaphore = new(); - private static Dictionary<string, RemoteHomeServer> _remoteHomeServerCache = new(); + private static Dictionary<string, RemoteHomeserver> _remoteHomeserverCache = new(); - public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedWithToken(string homeserver, string accessToken, - string? proxy = null) { + public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedWithToken(string homeserver, string accessToken, string? proxy = null) { var sem = _authenticatedHomeserverSemaphore.GetOrCreate(homeserver + accessToken, _ => new SemaphoreSlim(1, 1)); await sem.WaitAsync(); - lock (_authenticatedHomeServerCache) { - if (_authenticatedHomeServerCache.ContainsKey(homeserver + accessToken)) { + lock (_authenticatedHomeserverCache) { + if (_authenticatedHomeserverCache.ContainsKey(homeserver + accessToken)) { sem.Release(); - return _authenticatedHomeServerCache[homeserver + accessToken]; + return _authenticatedHomeserverCache[homeserver + accessToken]; } } - var domain = proxy ?? await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver); + // var domain = proxy ?? (await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver)).client; + + var rhs = await RemoteHomeserver.Create(homeserver); + var serverVersion = await rhs.GetServerVersionAsync(); + AuthenticatedHomeserverGeneric hs; if (true) { @@ -44,15 +47,15 @@ public class HomeserverProviderService { // (() => hs.WhoAmI) = (await hs._httpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami"))!; - lock(_authenticatedHomeServerCache) - _authenticatedHomeServerCache[homeserver + accessToken] = hs; + lock (_authenticatedHomeserverCache) + _authenticatedHomeserverCache[homeserver + accessToken] = hs; sem.Release(); return hs; } - public async Task<RemoteHomeServer> GetRemoteHomeserver(string homeserver, string? proxy = null) { - var hs = await RemoteHomeServer.Create(proxy ?? await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver)); + public async Task<RemoteHomeserver> GetRemoteHomeserver(string homeserver, string? proxy = null) { + var hs = await RemoteHomeserver.Create(proxy ?? homeserver); // hs._httpClient.Dispose(); // hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.ServerName) }; // hs._httpClient.Timeout = TimeSpan.FromSeconds(120); @@ -65,8 +68,8 @@ public class HomeserverProviderService { Identifier = new LoginRequest.LoginIdentifier { User = user }, Password = password }; - var resp = await hs._httpClient.PostAsJsonAsync("/_matrix/client/v3/login", payload); + var resp = await hs.ClientHttpClient.PostAsJsonAsync("/_matrix/client/v3/login", payload); var data = await resp.Content.ReadFromJsonAsync<LoginResponse>(); return data!; } -} +} \ No newline at end of file diff --git a/LibMatrix/Services/HomeserverResolverService.cs b/LibMatrix/Services/HomeserverResolverService.cs index 75545db..06771b0 100644 --- a/LibMatrix/Services/HomeserverResolverService.cs +++ b/LibMatrix/Services/HomeserverResolverService.cs @@ -8,42 +8,28 @@ namespace LibMatrix.Services; public class HomeserverResolverService(ILogger<HomeserverResolverService>? logger = null) { private readonly MatrixHttpClient _httpClient = new(); - private static readonly Dictionary<string, string> _wellKnownCache = new(); + private static readonly Dictionary<string, (string, string)> _wellKnownCache = new(); private static readonly Dictionary<string, SemaphoreSlim> _wellKnownSemaphores = new(); - public async Task<string> ResolveHomeserverFromWellKnown(string homeserver) { + public async Task<(string client, string server)> ResolveHomeserverFromWellKnown(string homeserver) { if (homeserver is null) throw new ArgumentNullException(nameof(homeserver)); - if(_wellKnownCache.TryGetValue(homeserver, out var known)) return known; - logger?.LogInformation("Resolving homeserver: {}", 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<string> _resolveHomeserverFromWellKnown(string homeserver) { - if (homeserver is null) throw new ArgumentNullException(nameof(homeserver)); - var sem = _wellKnownSemaphores.GetOrCreate(homeserver, _ => new SemaphoreSlim(1, 1)); - if(_wellKnownCache.TryGetValue(homeserver, out var wellKnown)) return wellKnown; - await sem.WaitAsync(); + // if(!_wellKnownSemaphores.ContainsKey(homeserver)) + // _wellKnownSemaphores[homeserver] = new(1, 1); + _wellKnownSemaphores.TryAdd(homeserver, new(1, 1)); + await _wellKnownSemaphores[homeserver].WaitAsync(); if (_wellKnownCache.TryGetValue(homeserver, out var known)) { - sem.Release(); + _wellKnownSemaphores[homeserver].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; + + logger?.LogInformation("Resolving homeserver: {}", homeserver); + var res = ( + await _tryResolveFromClientWellknown(homeserver), + await _tryResolveFromServerWellknown(homeserver) + ); + _wellKnownCache.Add(homeserver, res!); + _wellKnownSemaphores[homeserver].Release(); + return res; } private async Task<string?> _tryResolveFromClientWellknown(string homeserver) { @@ -63,6 +49,8 @@ public class HomeserverResolverService(ILogger<HomeserverResolverService>? logge if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/server")) { var resp = await _httpClient.GetFromJsonAsync<JsonElement>($"{homeserver}/.well-known/matrix/server"); var hs = resp.GetProperty("m.server").GetString(); + if (!hs.StartsWithAnyOf("http://", "https://")) + hs = $"https://{hs}"; return hs; } @@ -70,24 +58,11 @@ public class HomeserverResolverService(ILogger<HomeserverResolverService>? logge return null; } - private async Task<string?> _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<string?> _tryCheckIfSubDomainHasHomeserver(string homeserver, string subdomain) { - homeserver = homeserver.Replace("https://", $"https://{subdomain}."); - return await _tryCheckIfDomainHasHomeserver(homeserver); - } - 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)); if (!mxc.StartsWith("mxc://")) throw new InvalidDataException("mxc must start with mxc://"); - homeserver = await ResolveHomeserverFromWellKnown(homeserver); + homeserver = (await ResolveHomeserverFromWellKnown(homeserver)).client; return mxc.Replace("mxc://", $"{homeserver}/_matrix/media/v3/download/"); } } diff --git a/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs b/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs index e23d4f4..8a976a7 100644 --- a/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs +++ b/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs @@ -6,7 +6,7 @@ namespace LibMatrix.Tests.Abstractions; public static class HomeserverAbstraction { public static async Task<AuthenticatedHomeserverGeneric> GetHomeserver() { - var rhs = await RemoteHomeServer.Create("https://matrixunittests.rory.gay"); + var rhs = await RemoteHomeserver.Create("https://matrixunittests.rory.gay"); // string username = Guid.NewGuid().ToString(); // string password = Guid.NewGuid().ToString(); string username = "@f1a2d2d6-1924-421b-91d0-893b347b2a49:matrixunittests.rory.gay"; @@ -45,7 +45,7 @@ public static class HomeserverAbstraction { } public static async Task<AuthenticatedHomeserverGeneric> GetRandomHomeserver() { - var rhs = await RemoteHomeServer.Create("https://matrixunittests.rory.gay"); + var rhs = await RemoteHomeserver.Create("https://matrixunittests.rory.gay"); LoginResponse reg = await rhs.RegisterAsync(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "Unit tests!"); var hs = await reg.GetAuthenticatedHomeserver("https://matrixunittests.rory.gay"); diff --git a/Tests/LibMatrix.Tests/Tests/ResolverTest.cs b/Tests/LibMatrix.Tests/Tests/ResolverTest.cs index 345508a..cece41b 100644 --- a/Tests/LibMatrix.Tests/Tests/ResolverTest.cs +++ b/Tests/LibMatrix.Tests/Tests/ResolverTest.cs @@ -21,7 +21,7 @@ public class ResolverTest : TestBed<TestFixture> { public async Task ResolveServer() { foreach (var (domain, expected) in _config.ExpectedHomeserverMappings) { var server = await _resolver.ResolveHomeserverFromWellKnown(domain); - Assert.Equal(expected, server); + Assert.Equal(expected, server.client); } } |