diff --git a/Errors.cs b/Errors.cs new file mode 100644 index 0000000..50d27d1 --- /dev/null +++ b/Errors.cs @@ -0,0 +1,17 @@ +namespace matrix_dotnet; + +public class LoginRequiredException : UnauthorizedAccessException { } + +public class MatrixApiError : Exception { + public string ErrorCode { get; } + public string ErrorMessage { get; } + public HttpResponseMessage Response { get; } + + public MatrixApiError(string errorCode, string errorMessage, HttpResponseMessage response, Exception? innerException) + : base(String.Format("Request to Matrix API failed: {0}: {1}", errorCode, errorMessage), innerException) { + ErrorCode = errorCode; + ErrorMessage = errorMessage; + Response = response; + } +} + diff --git a/MatrixApi.cs b/MatrixApi.cs index 32dd060..7ce4199 100644 --- a/MatrixApi.cs +++ b/MatrixApi.cs @@ -2,13 +2,18 @@ namespace matrix_dotnet; using Refit; +/// +/// The IMatrixApi interface represents API endpoints directly and its implementation gets generated by Refit. +/// public interface IMatrixApi { + public record ErrorResponse(string errcode, string error, bool? soft_logout = null); public abstract record Identifier(string Type, string? User = null, string? Country = null, string? Phone = null, string? Medium = null, string? Address = null); public record UserIdentifier(string user) : Identifier("m.id.user", User: user); public record PhoneIdentifier(string country, string phone) : Identifier("m.id.phone", Country: country, Phone: phone); public record ThirdpartyIdentifier(string medium, string address) : Identifier("m.id.thirdparty", Medium: medium, Address: address); + /// The return value of the function. public record LoginResponse( string access_token, string device_id, @@ -19,6 +24,7 @@ public record LoginResponse( string user_id ); + /// public abstract record LoginRequest( string type, Identifier identifier, @@ -29,6 +35,7 @@ public abstract record LoginRequest( bool refresh_token = true ); + /// public record PasswordLoginRequest( Identifier identifier, string password, @@ -37,6 +44,7 @@ public record PasswordLoginRequest( bool refresh_token = true ) : LoginRequest("m.login.password", identifier, password, null, initial_device_display_name, device_id, refresh_token); + /// public record TokenLoginRequest( Identifier identifier, string token, @@ -67,3 +75,4 @@ public record SendEventResponse(string event_id); [Headers("Authorization: Bearer")] public Task SendEvent(string roomId, string eventType, string txnId, T body); } + diff --git a/MatrixClient.cs b/MatrixClient.cs index 5e0f3ae..a4a9907 100644 --- a/MatrixClient.cs +++ b/MatrixClient.cs @@ -26,51 +26,23 @@ public bool Expired { get => AccessToken is not null && ExpiresAt is not null && ExpiresAt < DateTime.Now; } - public class LoginRequiredException : UnauthorizedAccessException { } - private async Task GetAccessToken(HttpRequestMessage request, CancellationToken ct) { if (!LoggedIn) throw new LoginRequiredException(); if (Expired) await Refresh(); return AccessToken!; } - public record ErrorResponse(string errcode, string error, bool? soft_logout = null); - - public class ApiError : Exception { - public string ErrorCode { get; } - public string ErrorMessage { get; } - public HttpResponseMessage Response { get; } - - public ApiError(string errorCode, string errorMessage, HttpResponseMessage response, Exception? innerException) - : base(String.Format("Request to Matrix API failed: {0}: {1}", errorCode, errorMessage), innerException) { - ErrorCode = errorCode; - ErrorMessage = errorMessage; - Response = response; - } - } - private RefitSettings RefitSettings; - private class RetryException : Exception { } - - private static async Task RetryAsync(Func> func) { - retry: - try { - return await func(); - } catch (RetryException) { - goto retry; - } - } - private async Task ExceptionFactory(HttpResponseMessage response) { if (response.IsSuccessStatusCode) return null; - var errorResponse = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); + var errorResponse = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); if (errorResponse is not null) { if (errorResponse.errcode == "M_UNKNOWN_TOKEN") { if (errorResponse.soft_logout is not null && errorResponse.soft_logout!.Value) { await Refresh(); - return new RetryException(); + return new Retry.RetryException(); } else { AccessToken = null; RefreshToken = null; @@ -79,7 +51,7 @@ private static async Task RetryAsync(Func> func) } } - return new ApiError(errorResponse.errcode, errorResponse.error, response, null); + return new MatrixApiError(errorResponse.errcode, errorResponse.error, response, null); } return await ApiException.Create( @@ -119,6 +91,11 @@ public MatrixClient(Uri homeserver, ILogger? logger = null) { Api = RestService.For(apiUrlB.Uri.ToString(), RefitSettings); } + private void UpdateExpiresAt(int? expiresInMs) { + if (expiresInMs is null) ExpiresAt = null; + else ExpiresAt = DateTime.Now.AddMilliseconds((double)expiresInMs); + } + private async Task Login(IMatrixApi.LoginRequest request) { var response = await Api.Login(request); @@ -129,11 +106,6 @@ private async Task Login(IMatrixApi.LoginRequest request) { UpdateExpiresAt(response.expires_in_ms); } - private void UpdateExpiresAt(int? expiresInMs) { - if (expiresInMs is null) ExpiresAt = null; - else ExpiresAt = DateTime.Now.AddMilliseconds((double)expiresInMs); - } - public async Task PasswordLogin(string username, string password, string? initialDeviceDisplayName = null, string? deviceId = null) => await PasswordLogin(new IMatrixApi.UserIdentifier(username), password, initialDeviceDisplayName, deviceId); @@ -157,8 +129,9 @@ public async Task Refresh() { UpdateExpiresAt(response.expires_in_ms); } + public async Task GetJoinedRooms() { - var response = await RetryAsync(async () => await Api.GetJoinedRooms()); + var response = await Retry.RetryAsync(async () => await Api.GetJoinedRooms()); return response.joined_rooms; } @@ -167,7 +140,7 @@ public abstract record Message(string body, string msgtype); public record TextMessage(string body) : Message(body: body, msgtype: "m.text"); public async Task SendMessage(string roomId, TMessage message) where TMessage : Message { - var response = await RetryAsync(async () => await Api.SendEvent(roomId, "m.room.message", GenerateTransactionId(), message)); + var response = await Retry.RetryAsync(async () => await Api.SendEvent(roomId, "m.room.message", GenerateTransactionId(), message)); return response.event_id; } diff --git a/Util.cs b/Util.cs new file mode 100644 index 0000000..bf26fdf --- /dev/null +++ b/Util.cs @@ -0,0 +1,15 @@ +namespace matrix_dotnet; + +class Retry { + public class RetryException : Exception {} + + public static async Task RetryAsync(Func> func) { + retry: + try { + return await func(); + } catch (RetryException) { + goto retry; + } + } +} +