Index: ObjMatrix.oc ================================================================== --- ObjMatrix.oc +++ ObjMatrix.oc @@ -1,5 +1,5 @@ package_format 1 -package_depends_on ObjOpenSSL +package_depends_on ObjFWTLS package_depends_on ObjSQLite3 LIBS="-lobjmatrix $LIBS" FRAMEWORK_LIBS="-framework ObjMatrix $FRAMEWORK_LIBS" Index: README.md ================================================================== --- README.md +++ README.md @@ -7,20 +7,19 @@ It is currently in early development stages. ## How to build it? -You need [ObjFW](https://objfw.nil.im), -[ObjOpenSSL](https://fossil.nil.im/objopenssl) and +You need [ObjFW](https://objfw.nil.im) and [ObjSQLite3](https://fossil.nil.im/objsqlite3) installed in order to do this. ObjMatrix uses modern Objective-C, and hence cannot be compiled with GCC, but only with Clang. So install Clang first and ObjFW will automatically pick it up. You can install them all like this: - $ for i in objfw objopenssl objsqlite3 objmatrix; do + $ for i in objfw objsqlite3 objmatrix; do fossil clone https://fossil.nil.im/$i $i.fossil && mkdir $i && cd $i && fossil open ../$i.fossil && ./autogen.sh && Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -14,14 +14,14 @@ AC_CHECK_TOOL(OBJFW_CONFIG, objfw-config) AS_IF([test x"$OBJFW_CONFIG" = x""], [ AC_MSG_ERROR(You need ObjFW and objfw-config installed!) ]) -AS_IF([$OBJFW_CONFIG --package ObjOpenSSL], [ - OBJFW_CONFIG_FLAGS="$OBJFW_CONFIG_FLAGS --package ObjOpenSSL" +AS_IF([$OBJFW_CONFIG --package ObjFWTLS], [ + OBJFW_CONFIG_FLAGS="$OBJFW_CONFIG_FLAGS --package ObjFWTLS" ], [ - AC_MSG_ERROR(ObjOpenSSL not found!) + AC_MSG_ERROR(ObjFWTLS not found!) ]) AS_IF([$OBJFW_CONFIG --package ObjSQLite3], [ OBJFW_CONFIG_FLAGS="$OBJFW_CONFIG_FLAGS --package ObjSQLite3" ], [ AC_MSG_ERROR(ObjSQLite3 not found!) Index: src/MTXClient.h ================================================================== --- src/MTXClient.h +++ src/MTXClient.h @@ -90,11 +90,11 @@ @property (readonly, nonatomic) OFString *accessToken; /** * @brief The homeserver used by the client. */ -@property (readonly, nonatomic) OFURL *homeserver; +@property (readonly, nonatomic) OFIRI *homeserver; /** * @brief The storage used by the client. */ @property (readonly, nonatomic) id storage; @@ -116,18 +116,18 @@ * homeserver. * * @param userID The user ID for the client * @param deviceID The device ID for the client * @param accessToken The access token for the client - * @param homeserver The URL of the homeserver + * @param homeserver The IRI of the homeserver * @param storage The storage the client should use * @return An autoreleased MTXClient */ + (instancetype)clientWithUserID: (OFString *)userID deviceID: (OFString *)deviceID accessToken: (OFString *)accessToken - homeserver: (OFURL *)homeserver + homeserver: (OFIRI *)homeserver storage: (id )storage; /** * @brief Logs into the homeserver and creates a new client. * @@ -137,11 +137,11 @@ * @param storage The storage the client should use * @param block A block to call once login succeeded or failed */ + (void)logInWithUser: (OFString *)user password: (OFString *)password - homeserver: (OFURL *)homeserver + homeserver: (OFIRI *)homeserver storage: (id )storage block: (MTXClientLoginBlock)block; /** * @brief Initializes an already allocated client with the specified access @@ -148,18 +148,18 @@ * token on the specified homeserver. * * @param userID The user ID for the client * @param deviceID The device ID for the client * @param accessToken The access token for the client - * @param homeserver The URL of the homeserver + * @param homeserver The IRI of the homeserver * @param storage The storage the client should use * @return An initialized MTXClient */ - (instancetype)initWithUserID: (OFString *)userID deviceID: (OFString *)deviceID accessToken: (OFString *)accessToken - homeserver: (OFURL *)homeserver + homeserver: (OFIRI *)homeserver storage: (id )storage OF_DESIGNATED_INITIALIZER; /** * @brief Starts the sync loop. Index: src/MTXClient.m ================================================================== --- src/MTXClient.m +++ src/MTXClient.m @@ -30,16 +30,16 @@ #import "MTXLogoutFailedException.h" #import "MTXSendMessageFailedException.h" #import "MTXSyncFailedException.h" static void -validateHomeserver(OFURL *homeserver) +validateHomeserver(OFIRI *homeserver) { if (![homeserver.scheme isEqual: @"http"] && ![homeserver.scheme isEqual: @"https"]) @throw [OFUnsupportedProtocolException - exceptionWithURL: homeserver]; + exceptionWithIRI: homeserver]; if (homeserver.path != nil && ![homeserver.path isEqual: @"/"]) @throw [OFInvalidArgumentException exception]; if (homeserver.user != nil || homeserver.password != nil || @@ -53,11 +53,11 @@ } + (instancetype)clientWithUserID: (OFString *)userID deviceID: (OFString *)deviceID accessToken: (OFString *)accessToken - homeserver: (OFURL *)homeserver + homeserver: (OFIRI *)homeserver storage: (id )storage { return [[[self alloc] initWithUserID: userID deviceID: deviceID accessToken: accessToken @@ -65,11 +65,11 @@ storage: storage] autorelease]; } + (void)logInWithUser: (OFString *)user password: (OFString *)password - homeserver: (OFURL *)homeserver + homeserver: (OFIRI *)homeserver storage: (id )storage block: (MTXClientLoginBlock)block { void *pool = objc_autoreleasePoolPush(); @@ -110,26 +110,28 @@ OFString *deviceID = response[@"device_id"]; OFString *accessToken = response[@"access_token"]; if (![userID isKindOfClass: OFString.class] || ![deviceID isKindOfClass: OFString.class] || ![accessToken isKindOfClass: OFString.class]) { - block(nil, [OFInvalidServerReplyException exception]); + block(nil, + [OFInvalidServerResponseException exception]); return; } - OFString *baseURL = + OFString *baseIRI = response[@"well_known"][@"m.homeserver"][@"base_url"]; - if (baseURL != nil && - ![baseURL isKindOfClass: OFString.class]) { - block(nil, [OFInvalidServerReplyException exception]); + if (baseIRI != nil && + ![baseIRI isKindOfClass: OFString.class]) { + block(nil, + [OFInvalidServerResponseException exception]); return; } - OFURL *realHomeserver; - if (baseURL != nil) { + OFIRI *realHomeserver; + if (baseIRI != nil) { @try { - realHomeserver = [OFURL URLWithString: baseURL]; + realHomeserver = [OFIRI IRIWithString: baseIRI]; } @catch (id e) { block(nil, e); return; } } else @@ -147,11 +149,11 @@ } - (instancetype)initWithUserID: (OFString *)userID deviceID: (OFString *)deviceID accessToken: (OFString *)accessToken - homeserver: (OFURL *)homeserver + homeserver: (OFIRI *)homeserver storage: (id )storage { self = [super init]; @try { @@ -210,15 +212,24 @@ void *pool = objc_autoreleasePoolPush(); MTXRequest *request = [self requestWithPath: @"/_matrix/client/r0/sync"]; unsigned long long timeoutMs = _syncTimeout * 1000; - OFMutableDictionary *query = - [OFMutableDictionary dictionaryWithObject: @(timeoutMs).stringValue - forKey: @"timeout"]; - query[@"since"] = [_storage nextBatchForDeviceID: _deviceID]; - request.query = query; + OFMutableArray *> *queryItems = + [OFMutableArray array]; + OFString *since = [_storage nextBatchForDeviceID: _deviceID]; + + [queryItems addObject: + [OFPair pairWithFirstObject: @"timeout" + secondObject: @(timeoutMs).stringValue]]; + + if (since != nil) + [queryItems addObject: + [OFPair pairWithFirstObject: @"since" + secondObject: since]]; + + request.queryItems = queryItems; [request performWithBlock: ^ (MTXResponse response, int statusCode, id exception) { if (exception != nil) { if (_syncExceptionHandler != NULL) _syncExceptionHandler(exception); @@ -236,11 +247,12 @@ OFString *nextBatch = response[@"next_batch"]; if (![nextBatch isKindOfClass: OFString.class]) { if (_syncExceptionHandler != NULL) _syncExceptionHandler( - [OFInvalidServerReplyException exception]); + [OFInvalidServerResponseException + exception]); return; } @try { [_storage transactionWithBlock: ^ { @@ -322,17 +334,19 @@ return; } OFArray *joinedRooms = response[@"joined_rooms"]; if (![joinedRooms isKindOfClass: OFArray.class]) { - block(nil, [OFInvalidServerReplyException exception]); + block(nil, + [OFInvalidServerResponseException exception]); return; } for (OFString *room in joinedRooms) { if (![room isKindOfClass: OFString.class]) { block(nil, - [OFInvalidServerReplyException exception]); + [OFInvalidServerResponseException + exception]); return; } } block(response[@"joined_rooms"], nil); @@ -363,11 +377,12 @@ return; } OFString *roomID = response[@"room_id"]; if (![roomID isKindOfClass: OFString.class]) { - block(nil, [OFInvalidServerReplyException exception]); + block(nil, + [OFInvalidServerResponseException exception]); return; } block(roomID, nil); }]; Index: src/MTXRequest.h ================================================================== --- src/MTXRequest.h +++ src/MTXRequest.h @@ -52,13 +52,13 @@ * Some requests are unauthenticated - for those, the access token is `nil`. */ @property (readonly, nonatomic, nullable) OFString *accessToken; /** - * @brief The URL of the homeserver to send the request to. + * @brief The IRI of the homeserver to send the request to. */ -@property (readonly, nonatomic) OFURL *homeserver; +@property (readonly, nonatomic) OFIRI *homeserver; /** * @brief The HTTP request method. * * Defaults to `OF_HTTP_REQUEST_METHOD_GET`. @@ -69,14 +69,14 @@ * @brief The path of the request. */ @property (copy, nonatomic) OFString *path; /** - * @brief The query for the request. + * @brief The query items for the request. */ @property (copy, nullable, nonatomic) - OFDictionary *query; + OFArray *> *queryItems; /** * @brief An optional body to send along with the request. * * This is a dictionary that gets serialized to JSON when the request is sent. @@ -90,11 +90,11 @@ * @param homeserver The homeserver the request will be sent to * @return An autoreleased MTXRequest */ + (instancetype)requestWithPath: (OFString *)path accessToken: (nullable OFString *)accessToken - homeserver: (OFURL *)homeserver; + homeserver: (OFIRI *)homeserver; /** * @brief Initializes an already allocated request with the specified access * token and homeserver. * @@ -102,11 +102,11 @@ * @param homeserver The homeserver the request will be sent to * @return An initialized MTXRequest */ - (instancetype)initWithPath: (OFString *)path accessToken: (nullable OFString *)accessToken - homeserver: (OFURL *)homeserver; + homeserver: (OFIRI *)homeserver; /** * @brief Performs the request and calls the specified block once the request * succeeded or failed. * Index: src/MTXRequest.m ================================================================== --- src/MTXRequest.m +++ src/MTXRequest.m @@ -28,20 +28,20 @@ MTXRequestBlock _block; } + (instancetype)requestWithPath: (OFString *)path accessToken: (OFString *)accessToken - homeserver: (OFURL *)homeserver + homeserver: (OFIRI *)homeserver { return [[[self alloc] initWithPath: path accessToken: accessToken homeserver: homeserver] autorelease]; } - (instancetype)initWithPath: (OFString *)path accessToken: (OFString *)accessToken - homeserver: (OFURL *)homeserver + homeserver: (OFIRI *)homeserver { self = [super init]; @try { _accessToken = [accessToken copy]; @@ -89,25 +89,25 @@ { void *pool = objc_autoreleasePoolPush(); if (_block != nil) /* Not the best exception to indicate it's already in-flight. */ - @throw [OFAlreadyConnectedException exception]; + @throw [OFAlreadyOpenException exceptionWithObject: self]; - OFMutableURL *requestURL = [[_homeserver mutableCopy] autorelease]; - requestURL.path = _path; - requestURL.queryDictionary = _query; + OFMutableIRI *requestIRI = [[_homeserver mutableCopy] autorelease]; + requestIRI.path = _path; + requestIRI.queryItems = _queryItems; OFMutableDictionary *headers = [OFMutableDictionary dictionary]; headers[@"User-Agent"] = @"ObjMatrix"; if (_accessToken != nil) headers[@"Authorization"] = [OFString stringWithFormat: @"Bearer %@", _accessToken]; if (_body != nil) headers[@"Content-Length"] = @(_body.count).stringValue; - OFHTTPRequest *request = [OFHTTPRequest requestWithURL: requestURL]; + OFHTTPRequest *request = [OFHTTPRequest requestWithIRI: requestIRI]; request.method = _method; request.headers = headers; OFHTTPClient *client = [OFHTTPClient client]; client.delegate = self; Index: src/exceptions/MTXLoginFailedException.h ================================================================== --- src/exceptions/MTXLoginFailedException.h +++ src/exceptions/MTXLoginFailedException.h @@ -26,20 +26,20 @@ OF_ASSUME_NONNULL_BEGIN @interface MTXLoginFailedException: OFException @property (readonly, nonatomic) OFString *user; -@property (readonly, nonatomic) OFURL *homeserver; +@property (readonly, nonatomic) OFIRI *homeserver; @property (readonly, nonatomic) int statusCode; @property (readonly, nonatomic) MTXResponse response; + (instancetype)exceptionWithUser: (OFString *)user - homeserver: (OFURL *)homeserver + homeserver: (OFIRI *)homeserver statusCode: (int)statusCode response: (MTXResponse)response; - (instancetype)initWithUser: (OFString *)user - homeserver: (OFURL *)homeserver + homeserver: (OFIRI *)homeserver statusCode: (int)statusCode response: (MTXResponse)response; @end OF_ASSUME_NONNULL_END Index: src/exceptions/MTXLoginFailedException.m ================================================================== --- src/exceptions/MTXLoginFailedException.m +++ src/exceptions/MTXLoginFailedException.m @@ -22,11 +22,11 @@ #import "MTXLoginFailedException.h" @implementation MTXLoginFailedException + (instancetype)exceptionWithUser: (OFString *)user - homeserver: (OFURL *)homeserver + homeserver: (OFIRI *)homeserver statusCode: (int)statusCode response: (MTXResponse)response { return [[[self alloc] initWithUser: user homeserver: homeserver @@ -33,11 +33,11 @@ statusCode: statusCode response: response] autorelease]; } - (instancetype)initWithUser: (OFString *)user - homeserver: (OFURL *)homeserver + homeserver: (OFIRI *)homeserver statusCode: (int)statusCode response: (MTXResponse)response { self = [super init]; ADDED tests/Tests.m Index: tests/Tests.m ================================================================== --- /dev/null +++ tests/Tests.m @@ -0,0 +1,148 @@ +/* + * 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 "ObjMatrix.h" + +@interface Tests: OFObject +@end + +OF_APPLICATION_DELEGATE(Tests) + +@implementation Tests +{ + MTXClient *_client; + OFString *_roomID; +} + +- (void)applicationDidFinishLaunching: (OFNotification *)notification +{ + __auto_type environment = OFApplication.environment; + if (environment[@"OBJMATRIX_USER"] == nil || + environment[@"OBJMATRIX_PASS"] == nil || + environment[@"OBJMATRIX_HS"] == nil) { + [OFStdErr writeString: @"Please set OBJMATRIX_USER, " + @"OBJMATRIX_PASS and OBJMATRIX_HS in " + @"the environment!\n"]; + [OFApplication terminateWithStatus: 1]; + } + + OFIRI *homeserver = [OFIRI IRIWithString: environment[@"OBJMATRIX_HS"]]; + id storage = + [MTXSQLite3Storage storageWithPath: @"tests.db"]; + [MTXClient logInWithUser: environment[@"OBJMATRIX_USER"] + password: environment[@"OBJMATRIX_PASS"] + homeserver: homeserver + storage: storage + block: ^ (MTXClient *client, id exception) { + if (exception != nil) { + OFLog(@"Error logging in: %@", exception); + [OFApplication terminateWithStatus: 1]; + } + + _client = [client retain]; + OFLog(@"Logged in client: %@", _client); + + [_client startSyncLoop]; + [self fetchRoomList]; + }]; +} + +- (void)fetchRoomList +{ + [_client fetchRoomListWithBlock: ^ (OFArray *rooms, + id exception) { + if (exception != nil) { + OFLog(@"Failed to fetch room list: %@", exception); + [OFApplication terminateWithStatus: 1]; + } + + OFLog(@"Fetched room list: %@", rooms); + + [self joinRoom]; + }]; +} + +- (void)joinRoom +{ + OFString *room = @"#test:nil.im"; + [_client joinRoom: room block: ^ (OFString *roomID, id exception) { + if (exception != nil) { + OFLog(@"Failed to join room %@: %@", room, exception); + [OFApplication terminateWithStatus: 1]; + } + + _roomID = [roomID copy]; + OFLog(@"Joined room %@", _roomID); + + [self sendMessage]; + }]; +} + +- (void)sendMessage +{ + [_client sendMessage: @"ObjMatrix test successful!" + roomID: _roomID + block: ^ (id exception) { + if (exception != nil) { + OFLog(@"Failed to send message to room %@: %@", + _roomID, exception); + [OFApplication terminateWithStatus: 1]; + } + + OFLog(@"Message sent to %@", _roomID); + + OFLog(@"Waiting 5 seconds before leaving room and logging out"); + + [self performSelector: @selector(leaveRoom) afterDelay: 5]; + }]; +} + +- (void)leaveRoom +{ + [_client leaveRoom: _roomID block: ^ (id exception) { + if (exception != nil) { + OFLog(@"Failed to leave room %@: %@", exception); + [OFApplication terminateWithStatus: 1]; + } + + OFLog(@"Left room %@", _roomID); + + [self logOut]; + }]; +} + +- (void)logOut +{ + [_client logOutWithBlock: ^ (id exception) { + if (exception != nil) { + OFLog(@"Failed to log out: %@\n", exception); + [OFApplication terminateWithStatus: 1]; + } + + OFLog(@"Logged out client"); + + [OFApplication terminate]; + }]; +} +@end DELETED tests/tests.m Index: tests/tests.m ================================================================== --- tests/tests.m +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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 "ObjMatrix.h" - -@interface Tests: OFObject -@end - -OF_APPLICATION_DELEGATE(Tests) - -@implementation Tests -{ - MTXClient *_client; - OFString *_roomID; -} - -- (void)applicationDidFinishLaunching -{ - __auto_type environment = OFApplication.environment; - if (environment[@"OBJMATRIX_USER"] == nil || - environment[@"OBJMATRIX_PASS"] == nil || - environment[@"OBJMATRIX_HS"] == nil) { - [OFStdErr writeString: @"Please set OBJMATRIX_USER, " - @"OBJMATRIX_PASS and OBJMATRIX_HS in " - @"the environment!\n"]; - [OFApplication terminateWithStatus: 1]; - } - - OFURL *homeserver = [OFURL URLWithString: environment[@"OBJMATRIX_HS"]]; - id storage = - [MTXSQLite3Storage storageWithPath: @"tests.db"]; - [MTXClient logInWithUser: environment[@"OBJMATRIX_USER"] - password: environment[@"OBJMATRIX_PASS"] - homeserver: homeserver - storage: storage - block: ^ (MTXClient *client, id exception) { - if (exception != nil) { - OFLog(@"Error logging in: %@", exception); - [OFApplication terminateWithStatus: 1]; - } - - _client = [client retain]; - OFLog(@"Logged in client: %@", _client); - - [_client startSyncLoop]; - [self fetchRoomList]; - }]; -} - -- (void)fetchRoomList -{ - [_client fetchRoomListWithBlock: ^ (OFArray *rooms, - id exception) { - if (exception != nil) { - OFLog(@"Failed to fetch room list: %@", exception); - [OFApplication terminateWithStatus: 1]; - } - - OFLog(@"Fetched room list: %@", rooms); - - [self joinRoom]; - }]; -} - -- (void)joinRoom -{ - OFString *room = @"#test:nil.im"; - [_client joinRoom: room block: ^ (OFString *roomID, id exception) { - if (exception != nil) { - OFLog(@"Failed to join room %@: %@", room, exception); - [OFApplication terminateWithStatus: 1]; - } - - _roomID = [roomID copy]; - OFLog(@"Joined room %@", _roomID); - - [self sendMessage]; - }]; -} - -- (void)sendMessage -{ - [_client sendMessage: @"ObjMatrix test successful!" - roomID: _roomID - block: ^ (id exception) { - if (exception != nil) { - OFLog(@"Failed to send message to room %@: %@", - _roomID, exception); - [OFApplication terminateWithStatus: 1]; - } - - OFLog(@"Message sent to %@", _roomID); - - OFLog(@"Waiting 5 seconds before leaving room and logging out"); - - [self performSelector: @selector(leaveRoom) afterDelay: 5]; - }]; -} - -- (void)leaveRoom -{ - [_client leaveRoom: _roomID block: ^ (id exception) { - if (exception != nil) { - OFLog(@"Failed to leave room %@: %@", exception); - [OFApplication terminateWithStatus: 1]; - } - - OFLog(@"Left room %@", _roomID); - - [self logOut]; - }]; -} - -- (void)logOut -{ - [_client logOutWithBlock: ^ (id exception) { - if (exception != nil) { - OFLog(@"Failed to log out: %@\n", exception); - [OFApplication terminateWithStatus: 1]; - } - - OFLog(@"Logged out client"); - - [OFApplication terminate]; - }]; -} -@end