Index: src/MTXClient.h ================================================================== --- src/MTXClient.h +++ src/MTXClient.h @@ -33,10 +33,17 @@ * @param exception If the login failed, an exception */ typedef void (^mtx_client_login_block_t)(MTXClient *_Nullable client, id _Nullable exception); +/** + * @brief A block called when the device was logged out. + * + * @param exception `nil` on success, otherwise an exception + */ +typedef void (^mtx_client_logout_block_t)(id _Nullable exception); + /** * @brief A class that represents a client. */ @interface MTXClient: OFObject /** @@ -90,8 +97,17 @@ */ - (instancetype)initWithUserID: (OFString *)userID deviceID: (OFString *)deviceID accessToken: (OFString *)accessToken homeserver: (OFURL *)homeserver OF_DESIGNATED_INITIALIZER; + +/** + * @brief Logs out the device and invalidates the access token. + * + * @warning The client can no longer be used after this succeeded! + * + * @param block The block to call when logging out succeeded or failed + */ +- (void)asyncLogOutWithBlock: (mtx_client_logout_block_t)block; @end OF_ASSUME_NONNULL_END Index: src/MTXClient.m ================================================================== --- src/MTXClient.m +++ src/MTXClient.m @@ -22,10 +22,11 @@ #import "MTXClient.h" #import "MTXRequest.h" #import "MTXLoginFailedException.h" +#import "MTXLogoutFailedException.h" static void validateHomeserver(OFURL *homeserver) { if (![homeserver.scheme isEqual: @"http"] && @@ -74,13 +75,12 @@ @"user": user }, @"password": password }; - [request asyncPerformWithBlock: - ^ (OFDictionary *response, int statusCode, - id exception) { + [request asyncPerformWithBlock: ^ (mtx_response_t response, + int statusCode, id exception) { if (exception != nil) { block(nil, exception); return; } @@ -168,6 +168,40 @@ @"\tAccess token = %@\n" @"\tHomeserver = %@\n" @">", self.class, _userID, _deviceID, _accessToken, _homeserver]; } + +- (MTXRequest *)requestWithPath: (OFString *)path +{ + return [MTXRequest requestWithPath: path + accessToken: _accessToken + homeserver: _homeserver]; +} + +- (void)asyncLogOutWithBlock: (mtx_client_logout_block_t)block +{ + void *pool = objc_autoreleasePoolPush(); + MTXRequest *request = + [self requestWithPath: @"/_matrix/client/r0/logout"]; + request.method = OF_HTTP_REQUEST_METHOD_POST; + [request asyncPerformWithBlock: ^ (mtx_response_t response, + int statusCode, id exception) { + if (exception != nil) { + block(exception); + return; + } + + if (statusCode != 200) { + block([MTXLogoutFailedException + exceptionWithClient: self + statusCode: statusCode + response: response]); + return; + } + + block(nil); + }]; + + objc_autoreleasePoolPop(pool); +} @end Index: src/MTXRequest.h ================================================================== --- src/MTXRequest.h +++ src/MTXRequest.h @@ -22,21 +22,27 @@ #import OF_ASSUME_NONNULL_BEGIN +/** + * @brief A response to a request. + * + * This is a typedef for `OFDictionary *`. + */ +typedef OFDictionary *mtx_response_t; + /** * @brief A block called with the response for an MTXRequest. * * @param response The response to the request, as a dictionary parsed from JSON * @param statusCode The HTTP status code returned for the request * @param exception The first exception that occurred during the request, * or `nil` on success */ -typedef void (^mtx_request_block_t)( - OFDictionary *_Nullable response, int statusCode, - id _Nullable exception); +typedef void (^mtx_request_block_t)(mtx_response_t _Nullable response, + int statusCode, id _Nullable exception); /** * @brief An internal class for performing a request on the Matrix server. */ @interface MTXRequest: OFObject Index: src/MTXRequest.m ================================================================== --- src/MTXRequest.m +++ src/MTXRequest.m @@ -99,12 +99,12 @@ requestURL.path = _path; OFMutableDictionary *headers = [OFMutableDictionary dictionary]; headers[@"User-Agent"] = @"ObjMatrix"; if (_accessToken != nil) - headers[@"Authentication"] = [OFString - stringWithFormat: @"Bearer %@", _accessToken]; + headers[@"Authorization"] = + [OFString stringWithFormat: @"Bearer %@", _accessToken]; if (_body != nil) headers[@"Content-Length"] = @(_body.count).stringValue; OFHTTPRequest *request = [OFHTTPRequest requestWithURL: requestURL]; request.method = _method; @@ -137,13 +137,13 @@ [responseData addItems: buffer count: length]; } - OFDictionary *responseJSON = - [OFString stringWithUTF8String: responseData.items - length: responseData.count] + mtx_response_t responseJSON = [OFString + stringWithUTF8String: responseData.items + length: responseData.count] .objectByParsingJSON; block(responseJSON, response.statusCode, nil); } @catch (id e) { block(nil, response.statusCode, e); Index: src/ObjMatrix.h ================================================================== --- src/ObjMatrix.h +++ src/ObjMatrix.h @@ -22,5 +22,6 @@ #import "MTXClient.h" #import "MTXRequest.h" #import "MTXLoginFailedException.h" +#import "MTXLogoutFailedException.h" Index: src/exceptions/MTXLoginFailedException.h ================================================================== --- src/exceptions/MTXLoginFailedException.h +++ src/exceptions/MTXLoginFailedException.h @@ -19,25 +19,27 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #import + +#import "MTXRequest.h" OF_ASSUME_NONNULL_BEGIN @interface MTXLoginFailedException: OFException @property (readonly, nonatomic) OFString *user; @property (readonly, nonatomic) OFURL *homeserver; @property (readonly, nonatomic) int statusCode; -@property (readonly, nonatomic) OFDictionary *response; +@property (readonly, nonatomic) mtx_response_t response; + (instancetype)exceptionWithUser: (OFString *)user homeserver: (OFURL *)homeserver statusCode: (int)statusCode - response: (OFDictionary *)response; + response: (mtx_response_t)response; - (instancetype)initWithUser: (OFString *)user homeserver: (OFURL *)homeserver statusCode: (int)statusCode - response: (OFDictionary *)response; + response: (mtx_response_t)response; @end OF_ASSUME_NONNULL_END Index: src/exceptions/MTXLoginFailedException.m ================================================================== --- src/exceptions/MTXLoginFailedException.m +++ src/exceptions/MTXLoginFailedException.m @@ -24,11 +24,11 @@ @implementation MTXLoginFailedException + (instancetype)exceptionWithUser: (OFString *)user homeserver: (OFURL *)homeserver statusCode: (int)statusCode - response: (OFDictionary *)response + response: (mtx_response_t)response { return [[[self alloc] initWithUser: user homeserver: homeserver statusCode: statusCode response: response] autorelease]; @@ -35,11 +35,11 @@ } - (instancetype)initWithUser: (OFString *)user homeserver: (OFURL *)homeserver statusCode: (int)statusCode - response: (OFDictionary *)response + response: (mtx_response_t)response { self = [super init]; @try { _user = [user copy]; @@ -60,6 +60,13 @@ [_homeserver release]; [_response release]; [super dealloc]; } + +- (OFString *)description +{ + return [OFString stringWithFormat: + @"Failed to log in user %@ on %@: %@", + _user, _homeserver, _response]; +} @end ADDED src/exceptions/MTXLogoutFailedException.h Index: src/exceptions/MTXLogoutFailedException.h ================================================================== --- /dev/null +++ src/exceptions/MTXLogoutFailedException.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020, Jonathan Schleifer + * + * https://fossil.nil.im/objmatrix + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +#import "MTXRequest.h" + +OF_ASSUME_NONNULL_BEGIN + +@class MTXClient; + +@interface MTXLogoutFailedException: OFException +@property (readonly, nonatomic) MTXClient *client; +@property (readonly, nonatomic) int statusCode; +@property (readonly, nonatomic) mtx_response_t response; + ++ (instancetype)exceptionWithClient: (MTXClient *)client + statusCode: (int)statusCode + response: (mtx_response_t)response; +- (instancetype)initWithClient: (OFString *)user + statusCode: (int)statusCode + response: (mtx_response_t)response; +@end + +OF_ASSUME_NONNULL_END ADDED src/exceptions/MTXLogoutFailedException.m Index: src/exceptions/MTXLogoutFailedException.m ================================================================== --- /dev/null +++ src/exceptions/MTXLogoutFailedException.m @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020, Jonathan Schleifer + * + * https://fossil.nil.im/objmatrix + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice is present in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "MTXLogoutFailedException.h" + +#import "MTXClient.h" + +@implementation MTXLogoutFailedException ++ (instancetype)exceptionWithClient: (MTXClient *)client + statusCode: (int)statusCode + response: (mtx_response_t)response +{ + return [[[self alloc] initWithClient: client + statusCode: statusCode + response: response] autorelease]; +} + +- (instancetype)initWithClient: (MTXClient *)client + statusCode: (int)statusCode + response: (mtx_response_t)response +{ + self = [super init]; + + @try { + _client = [client retain]; + _statusCode = statusCode; + _response = [response copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_client release]; + [_response release]; + + [super dealloc]; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: + @"Failed to log out user %@: %@", _client.userID, _response]; +} +@end Index: src/exceptions/Makefile ================================================================== --- src/exceptions/Makefile +++ src/exceptions/Makefile @@ -1,11 +1,12 @@ include ../../extra.mk STATIC_PIC_LIB_NOINST = ${EXCEPTIONS_LIB_A} STATIC_LIB_NOINST = ${EXCEPTIONS_A} -SRCS = MTXLoginFailedException.m +SRCS = MTXLoginFailedException.m \ + MTXLogoutFailedException.m INCLUDES = ${SRCS:.m=.h} include ../../buildsys.mk CPPFLAGS += -I. -I.. Index: tests/tests.m ================================================================== --- tests/tests.m +++ tests/tests.m @@ -46,19 +46,24 @@ [MTXClient logInWithUser: environment[@"OBJMATRIX_USER"] password: environment[@"OBJMATRIX_PASS"] homeserver: homeserver block: ^ (MTXClient *client, id exception) { if (exception != nil) { - [of_stdout writeFormat: @"Error logging in: %@\n", - exception]; - if ([exception isKindOfClass: - MTXLoginFailedException.class]) - [of_stdout writeFormat: @"Response: %@\n", - [exception response]]; + of_log(@"Error logging in: %@", exception); [OFApplication terminateWithStatus: 1]; } - [of_stdout writeFormat: @"Logged in client: %@\n", client]; - [OFApplication terminate]; + of_log(@"Logged in client: %@", client); + + [client asyncLogOutWithBlock: ^ (id exception) { + if (exception != nil) { + of_log(@"Failed to log out: %@\n", exception); + [OFApplication terminateWithStatus: 1]; + } + + of_log(@"Logged out client"); + + [OFApplication terminate]; + }]; }]; } @end