From 3a070e737614fc7531d61ca3ae1cd871ad550895 Mon Sep 17 00:00:00 2001 From: giomfo Date: Fri, 18 Mar 2016 09:41:35 +0100 Subject: [PATCH 01/30] Bug Fix: Search: 'no result' label is persistent #75 --- MatrixKit/Controllers/MXKSearchViewController.m | 1 + 1 file changed, 1 insertion(+) diff --git a/MatrixKit/Controllers/MXKSearchViewController.m b/MatrixKit/Controllers/MXKSearchViewController.m index 71aee0389..5caae033c 100644 --- a/MatrixKit/Controllers/MXKSearchViewController.m +++ b/MatrixKit/Controllers/MXKSearchViewController.m @@ -279,6 +279,7 @@ - (void)dataSource:(MXKDataSource*)dataSource2 didStateChange:(MXKDataSourceStat // Display "No Results" if there is nothing if ([dataSource tableView:_searchTableView numberOfRowsInSection:0]) { + _noResultsLabel.hidden = YES; _searchTableView.hidden = NO; } else From 8cea488585eee491028c4d0a9cf7c395febb1286 Mon Sep 17 00:00:00 2001 From: giomfo Date: Fri, 18 Mar 2016 17:53:40 +0100 Subject: [PATCH 02/30] MXKRecentsDataSource: Remove room notifications and room tags handling. --- .../Models/RoomList/MXKRecentsDataSource.h | 24 -- .../Models/RoomList/MXKRecentsDataSource.m | 266 +----------------- 2 files changed, 1 insertion(+), 289 deletions(-) diff --git a/MatrixKit/Models/RoomList/MXKRecentsDataSource.h b/MatrixKit/Models/RoomList/MXKRecentsDataSource.h index aa905b0f3..b203d6cec 100644 --- a/MatrixKit/Models/RoomList/MXKRecentsDataSource.h +++ b/MatrixKit/Models/RoomList/MXKRecentsDataSource.h @@ -130,30 +130,6 @@ */ - (void)leaveRoomAtIndexPath:(NSIndexPath *)indexPath; -/** - Update the room tag at the index path - - @param indexPath the index of the cell - @param tag the new tag value - */ -- (void)updateRoomTagAtIndexPath:(NSIndexPath *)indexPath to:(NSString*)tag; - -/** - Check if the room receives pushes. - - @param indexPath the index of the cell - @return YES if the room is notified. - */ -- (BOOL)isRoomNotifiedAtIndexPath:(NSIndexPath *)indexPath; - -/** - Mute/unmute the room notifications to the room selected at the index path indexPath - - @param mute YES to mute room notification - @param indexPath the index of the cell - */ -- (void)muteRoomNotifications:(BOOL)mute atIndexPath:(NSIndexPath *)indexPath; - /** Action registered on buttons used to shrink/disclose recents sources. */ diff --git a/MatrixKit/Models/RoomList/MXKRecentsDataSource.m b/MatrixKit/Models/RoomList/MXKRecentsDataSource.m index cce1d3308..de12b3543 100644 --- a/MatrixKit/Models/RoomList/MXKRecentsDataSource.m +++ b/MatrixKit/Models/RoomList/MXKRecentsDataSource.m @@ -38,14 +38,6 @@ @interface MXKRecentsDataSource () The current search pattern list */ NSArray* searchPatternsList; - - /* - While muting a room, the dedicated rule might be deleted before creating a new one. - The creation must be done after the deletion has been confirmed. - The confirmation is done with a notification. - */ - NSMutableDictionary* ruleDidUpdateObserverByRoomId; - NSMutableDictionary* ruleDidFailUpdateObserverByRoomId; } @end @@ -65,9 +57,6 @@ - (instancetype)init // Set default data and view classes [self registerCellDataClass:MXKRecentCellData.class forCellIdentifier:kMXKRecentCellIdentifier]; - - ruleDidUpdateObserverByRoomId = [[NSMutableDictionary alloc] init]; - ruleDidFailUpdateObserverByRoomId = [[NSMutableDictionary alloc] init]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionInviteRoomUpdate:) name:kMXSessionInvitedRoomsDidChangeNotification object:nil]; } @@ -206,29 +195,6 @@ - (MXKDataSourceState)state - (void)destroy { - // remove any observer - if (ruleDidUpdateObserverByRoomId || ruleDidFailUpdateObserverByRoomId) - { - NSMutableArray *observers = [[NSMutableArray alloc] init]; - - if (ruleDidUpdateObserverByRoomId) - { - [observers addObjectsFromArray:[ruleDidUpdateObserverByRoomId allValues]]; - ruleDidUpdateObserverByRoomId = nil; - } - - if (ruleDidFailUpdateObserverByRoomId) - { - [observers addObjectsFromArray:[ruleDidFailUpdateObserverByRoomId allValues]]; - ruleDidFailUpdateObserverByRoomId = nil; - } - - for(id observer in observers) - { - [[NSNotificationCenter defaultCenter] removeObserver:observer]; - } - } - for (MXKSessionRecentsDataSource *recentsDataSource in recentsDataSourceArray) { [recentsDataSource destroy]; @@ -643,7 +609,7 @@ - (void)leaveRoomAtIndexPath:(NSIndexPath *)indexPath [room leave:^{ - // Refresh table display + // Trigger recents table refresh if (self.delegate) { [self.delegate dataSource:self didCellChange:nil]; @@ -655,241 +621,11 @@ - (void)leaveRoomAtIndexPath:(NSIndexPath *)indexPath // Notify MatrixKit user [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error]; - }]; - } -} - -/** - Update the room tag at the index path - - @param indexPath the index of the cell - @param tag the new tag value - */ -- (void)updateRoomTagAtIndexPath:(NSIndexPath *)indexPath to:(NSString*)newtag -{ - MXRoom* room = [self getRoomAtIndexPath:indexPath]; - - if (room) - { - NSString* oldTag = nil; - - // sanity cg - if (room.accountData.tags && room.accountData.tags.count) - { - oldTag = [room.accountData.tags.allKeys objectAtIndex:0]; - } - - // support only kMXRoomTagFavourite or kMXRoomTagLowPriority tags by now - if (![newtag isEqualToString:kMXRoomTagFavourite] && ![newtag isEqualToString:kMXRoomTagLowPriority]) - { - newtag = nil; - } - - NSString* tagOrder = [room.mxSession tagOrderToBeAtIndex:0 from:NSNotFound withTag:newtag]; - - NSLog(@"[MXKRecentsDataSource] Update the room %@ tag from %@ to %@ with tag order %@", room.state.roomId, oldTag, newtag, tagOrder); - - [room replaceTag:oldTag - byTag:newtag - withOrder:tagOrder - success: ^{ - // Refresh table display - if (self.delegate) - { - [self.delegate dataSource:self didCellChange:nil]; - } - - } failure:^(NSError *error) { - - NSLog(@"[MXKRecentsDataSource] Failed to update the tag %@ of room (%@) failed: %@", newtag, room.state.roomId, error); - - // Notify MatrixKit user - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error]; }]; } } -- (MXPushRule*)getPushRulesForRoom:(MXRoom*)room -{ - if (room) - { - NSArray* rules = room.mxSession.notificationCenter.rules.global.room; - - // sanity checks - if (rules) - { - for(MXPushRule* rule in rules) - { - // the rule id is the room Id - // it is the server trick to avoid duplicated rule on the same room. - if ([rule.ruleId isEqualToString:room.state.roomId]) - { - return rule; - } - } - } - } - - return nil; -} - -/** - Check if there is a push notification rules for the room at the position indexPath - - @param indexPath the index of the cell - @return YES if there is a push rules. - */ -- (BOOL)isRoomNotifiedAtIndexPath:(NSIndexPath *)indexPath -{ - MXRoom* room = [self getRoomAtIndexPath:indexPath]; - MXPushRule* rule = [self getPushRulesForRoom:room]; - - if (rule) - { - for (MXPushRuleAction *ruleAction in rule.actions) - { - if (ruleAction.actionType == MXPushRuleActionTypeDontNotify) - { - return !rule.enabled; - } - } - } - - return YES; -} - -- (void)muteRoomNotifications:(BOOL)mute atIndexPath:(NSIndexPath *)indexPath -{ - MXRoom* room = [self getRoomAtIndexPath:indexPath]; - - // sanity check - if (room) - { - BOOL isNotified = [self isRoomNotifiedAtIndexPath:indexPath]; - - // check if the state is already in the right state - if (isNotified == !mute) - { - return; - } - - MXNotificationCenter* notificationCenter = room.mxSession.notificationCenter; - MXPushRule* rule = [self getPushRulesForRoom:room]; - - if (!mute) - { - // let the other notification rules manage the pushes. - [notificationCenter removeRule:rule]; - } - else - { - // user does not want to have push - - // if there is no rule - if (!rule) - { - // add one - [notificationCenter addRoomRule:room.state.roomId - notify:NO - sound:NO - highlight:NO]; - } - else - { - // check if there is no pending update for this room - if ([ruleDidUpdateObserverByRoomId objectForKey:room.state.roomId]) - { - // if there is one, ignore the current request - return; - } - - - // check if the user did not define one - BOOL hasDontNotifyRule = NO; - - for (MXPushRuleAction *ruleAction in rule.actions) - { - if (ruleAction.actionType == MXPushRuleActionTypeDontNotify) - { - hasDontNotifyRule = YES; - break; - } - } - - // if the user defined one, use it - if (hasDontNotifyRule) - { - [notificationCenter enableRule:rule isEnabled:YES]; - } - else - { - // if the user defined a room rule - // the rule is deleted before adding new one - - id notificationCenterDidUpdateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidUpdateRules object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { - - MXPushRule* rule = [self getPushRulesForRoom:room]; - - // check if the rule has been deleted - // there is no way to know if the notif is really for this rule.. - if (!rule) - { - id observer = [ruleDidUpdateObserverByRoomId objectForKey:room.state.roomId]; - - if (observer) - { - [[NSNotificationCenter defaultCenter] removeObserver:observer]; - [ruleDidUpdateObserverByRoomId removeObjectForKey:room.state.roomId]; - } - - observer = [ruleDidFailUpdateObserverByRoomId objectForKey:room.state.roomId]; - - if (observer) - { - [[NSNotificationCenter defaultCenter] removeObserver:observer]; - [ruleDidFailUpdateObserverByRoomId removeObjectForKey:room.state.roomId]; - } - - // add one - [notificationCenter addRoomRule:room.state.roomId - notify:NO - sound:NO - highlight:NO]; - } - }]; - - id notificationCenterDidFailObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXNotificationCenterDidFailRulesUpdate object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { - - id observer = [ruleDidUpdateObserverByRoomId objectForKey:room.state.roomId]; - - if (observer) - { - [[NSNotificationCenter defaultCenter] removeObserver:observer]; - [ruleDidUpdateObserverByRoomId removeObjectForKey:room.state.roomId]; - } - - observer = [ruleDidFailUpdateObserverByRoomId objectForKey:room.state.roomId]; - - if (observer) - { - [[NSNotificationCenter defaultCenter] removeObserver:observer]; - [ruleDidFailUpdateObserverByRoomId removeObjectForKey:room.state.roomId]; - } - }]; - - [ruleDidUpdateObserverByRoomId setObject:notificationCenterDidUpdateObserver forKey:room.state.roomId]; - [ruleDidFailUpdateObserverByRoomId setObject:notificationCenterDidFailObserver forKey:room.state.roomId]; - - // remove the rule notification - // the notifications are used to tell - [notificationCenter removeRule:rule]; - } - } - } - } -} - - (void)didMXSessionInviteRoomUpdate:(NSNotification *)notif { MXSession *mxSession = notif.object; From c8cae50e126d8f6a9d6af66b899bf00eea5f47d6 Mon Sep 17 00:00:00 2001 From: giomfo Date: Mon, 21 Mar 2016 14:41:36 +0100 Subject: [PATCH 03/30] MXKAccount: the push gateway URL must be configurable #76 --- MatrixKit/Models/Account/MXKAccount.h | 6 ++++++ MatrixKit/Models/Account/MXKAccount.m | 27 ++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/MatrixKit/Models/Account/MXKAccount.h b/MatrixKit/Models/Account/MXKAccount.h index ff077b784..3ab32d8c3 100644 --- a/MatrixKit/Models/Account/MXKAccount.h +++ b/MatrixKit/Models/Account/MXKAccount.h @@ -60,6 +60,12 @@ typedef BOOL (^MXKAccountOnCertificateChange)(MXKAccount *mxAccount, NSData *cer */ @property (nonatomic) NSString *identityServerURL; +/** + The Push Gateway URL used to send event notifications to (nil by default). + This URL should be over HTTPS and never over HTTP. + */ +@property (nonatomic) NSString *pushGatewayURL; + /** The matrix REST client used to make matrix API requests. */ diff --git a/MatrixKit/Models/Account/MXKAccount.m b/MatrixKit/Models/Account/MXKAccount.m index b54727606..6fbb8ed37 100644 --- a/MatrixKit/Models/Account/MXKAccount.m +++ b/MatrixKit/Models/Account/MXKAccount.m @@ -161,6 +161,11 @@ - (id)initWithCoder:(NSCoder *)coder } } + if ([coder decodeObjectForKey:@"pushgatewayurl"]) + { + _pushGatewayURL = [coder decodeObjectForKey:@"pushgatewayurl"]; + } + _enablePushNotifications = [coder decodeBoolForKey:@"_enablePushNotifications"]; _enableInAppNotifications = [coder decodeBoolForKey:@"enableInAppNotifications"]; @@ -186,6 +191,11 @@ - (void)encodeWithCoder:(NSCoder *)coder [coder encodeObject:_identityServerURL forKey:@"identityserverurl"]; } + if (self.pushGatewayURL) + { + [coder encodeObject:_pushGatewayURL forKey:@"pushgatewayurl"]; + } + [coder encodeBool:_enablePushNotifications forKey:@"_enablePushNotifications"]; [coder encodeBool:_enableInAppNotifications forKey:@"enableInAppNotifications"]; @@ -213,6 +223,14 @@ - (void)setIdentityServerURL:(NSString *)identityServerURL [[MXKAccountManager sharedManager] saveAccounts]; } +- (void)setPushGatewayURL:(NSString *)pushGatewayURL +{ + _pushGatewayURL = pushGatewayURL.length ? pushGatewayURL : nil; + + // Archive updated field + [[MXKAccountManager sharedManager] saveAccounts]; +} + - (NSString*)userDisplayName { if (mxSession) @@ -629,6 +647,13 @@ - (void)enablePusher:(BOOL)enabled success:(void (^)())success failure:(void (^) return; } + // Check whether the Push Gateway URL has been configured. + if (!self.pushGatewayURL) + { + NSLog(@"[MXKAccount] Not setting pusher because the Push Gateway URL is undefined"); + return; + } + #ifdef DEBUG NSString *appId = [[NSUserDefaults standardUserDefaults] objectForKey:@"pusherAppIdDev"]; #else @@ -645,7 +670,7 @@ - (void)enablePusher:(BOOL)enabled success:(void (^)())success failure:(void (^) NSString *b64Token = [[MXKAccountManager sharedManager].apnsDeviceToken base64EncodedStringWithOptions:0]; NSDictionary *pushData = @{ - @"url": @"https://matrix.org/_matrix/push/v1/notify", + @"url": self.pushGatewayURL, }; NSString *deviceLang = [NSLocale preferredLanguages][0]; From 7e966cdc72bdeeb8936ad94672b172041086159c Mon Sep 17 00:00:00 2001 From: giomfo Date: Mon, 21 Mar 2016 18:30:53 +0100 Subject: [PATCH 04/30] Bug Fix: Multiple invitations on Start Chat action. --- .../MXKContactDetailsViewController.h | 3 +- .../MXKContactDetailsViewController.m | 8 +++- .../MXKContactListViewController.h | 15 +++++++ .../MXKContactListViewController.m | 44 ++++++++++++++++++- .../MXKRoomMemberDetailsViewController.h | 13 +++++- .../MXKRoomMemberDetailsViewController.m | 11 +++-- 6 files changed, 86 insertions(+), 8 deletions(-) diff --git a/MatrixKit/Controllers/MXKContactDetailsViewController.h b/MatrixKit/Controllers/MXKContactDetailsViewController.h index 73f9be7a5..3d2e13222 100644 --- a/MatrixKit/Controllers/MXKContactDetailsViewController.h +++ b/MatrixKit/Controllers/MXKContactDetailsViewController.h @@ -32,8 +32,9 @@ @param contactDetailsViewController the `MXKContactDetailsViewController` instance. @param matrixId the selected matrix id of the contact. + @param completion the block to execute at the end of the operation (independently if it succeeded or not). */ -- (void)contactDetailsViewController:(MXKContactDetailsViewController *)contactDetailsViewController startChatWithMatrixId:(NSString*)matrixId; +- (void)contactDetailsViewController:(MXKContactDetailsViewController *)contactDetailsViewController startChatWithMatrixId:(NSString*)matrixId completion:(void (^)(void))completion; @end diff --git a/MatrixKit/Controllers/MXKContactDetailsViewController.m b/MatrixKit/Controllers/MXKContactDetailsViewController.m index 09f81d580..a20845a24 100644 --- a/MatrixKit/Controllers/MXKContactDetailsViewController.m +++ b/MatrixKit/Controllers/MXKContactDetailsViewController.m @@ -167,7 +167,13 @@ - (void)startChat:(UIButton*)sender { if (self.delegate && sender.tag < matrixIDs.count) { - [self.delegate contactDetailsViewController:self startChatWithMatrixId:[matrixIDs objectAtIndex:sender.tag]]; + sender.enabled = NO; + + [self.delegate contactDetailsViewController:self startChatWithMatrixId:[matrixIDs objectAtIndex:sender.tag] completion:^{ + + sender.enabled = YES; + + }]; } } diff --git a/MatrixKit/Controllers/MXKContactListViewController.h b/MatrixKit/Controllers/MXKContactListViewController.h index c76d7573e..7a13ab7f2 100644 --- a/MatrixKit/Controllers/MXKContactListViewController.h +++ b/MatrixKit/Controllers/MXKContactListViewController.h @@ -70,6 +70,11 @@ */ @property (nonatomic) BOOL enableSearch; +/** + Tell whether an action is already in progress. + */ +@property (nonatomic, readonly) BOOL hasPendingAction; + /** The class used in creating new contact table cells. Only MXKContactTableCell classes or sub-classes are accepted. @@ -103,5 +108,15 @@ */ - (IBAction)onSegmentValueChange:(id)sender; +/** + Add a mask in overlay to prevent a new contact selection (used when an action is on progress). + */ +- (void)addPendingActionMask; + +/** + Remove the potential overlay mask + */ +- (void)removePendingActionMask; + @end diff --git a/MatrixKit/Controllers/MXKContactListViewController.m b/MatrixKit/Controllers/MXKContactListViewController.m index 65eb33ce1..81d5500e9 100644 --- a/MatrixKit/Controllers/MXKContactListViewController.m +++ b/MatrixKit/Controllers/MXKContactListViewController.m @@ -46,6 +46,10 @@ @interface MXKContactListViewController () NSString* latestSearchedPattern; NSArray* collationTitles; + + // mask view while processing a request + UIView* pendingRequestMask; + UIActivityIndicatorView * pendingMaskSpinnerView; } @end @@ -66,10 +70,18 @@ + (instancetype)contactListViewController bundle:[NSBundle bundleForClass:[MXKContactListViewController class]]]; } -- (void)dealloc{ +- (void)dealloc +{ searchButton = nil; } +- (void)destroy +{ + [self removePendingActionMask]; + + [super destroy]; +} + - (void)viewDidLoad { [super viewDidLoad]; @@ -196,6 +208,36 @@ - (void)updateSectionedMatrixContacts:(BOOL)force } } +- (BOOL)hasPendingAction +{ + return nil != pendingMaskSpinnerView; +} + +- (void)addPendingActionMask +{ + // add a spinner above the tableview to avoid that the user tap on any other button + pendingMaskSpinnerView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; + pendingMaskSpinnerView.backgroundColor = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:0.5]; + pendingMaskSpinnerView.frame = self.tableView.frame; + pendingMaskSpinnerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin; + + // append it + [self.tableView.superview addSubview:pendingMaskSpinnerView]; + + // animate it + [pendingMaskSpinnerView startAnimating]; +} + +- (void)removePendingActionMask +{ + if (pendingMaskSpinnerView) + { + [pendingMaskSpinnerView removeFromSuperview]; + pendingMaskSpinnerView = nil; + [self.tableView reloadData]; + } +} + #pragma mark - UITableView dataSource - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView diff --git a/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.h b/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.h index 158bc7d4c..06a110598 100644 --- a/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.h +++ b/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.h @@ -51,8 +51,9 @@ typedef enum : NSUInteger @param roomMemberDetailsViewController the `MXKRoomMemberDetailsViewController` instance. @param matrixId the member's matrix id + @param completion the block to execute at the end of the operation (independently if it succeeded or not). */ -- (void)roomMemberDetailsViewController:(MXKRoomMemberDetailsViewController *)roomMemberDetailsViewController startChatWithMemberId:(NSString*)matrixId; +- (void)roomMemberDetailsViewController:(MXKRoomMemberDetailsViewController *)roomMemberDetailsViewController startChatWithMemberId:(NSString*)matrixId completion:(void (^)(void))completion; @optional @@ -165,5 +166,15 @@ typedef enum : NSUInteger */ - (void)setPowerLevel:(NSInteger)value; +/** + Add a mask in overlay to prevent a new contact selection (used when an action is on progress). + */ +- (void)addPendingActionMask; + +/** + Remove the potential overlay mask + */ +- (void)removePendingActionMask; + @end diff --git a/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m b/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m index db969ef38..589d1ffb1 100644 --- a/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m +++ b/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.m @@ -100,6 +100,8 @@ - (void)destroy self.actionMenu = nil; } + [self removePendingActionMask]; + [self removeObservers]; self.delegate = nil; @@ -146,7 +148,7 @@ - (IBAction)onActionButtonPressed:(id)sender { if ([sender isKindOfClass:[UIButton class]]) { - // already a pending action + // Check whether an action is already in progress if ([self hasPendingAction]) { return; @@ -276,9 +278,10 @@ - (IBAction)onActionButtonPressed:(id)sender { [self addPendingActionMask]; - [self.delegate roomMemberDetailsViewController:self startChatWithMemberId:_mxRoomMember.userId]; - - [self removePendingActionMask]; + [self.delegate roomMemberDetailsViewController:self startChatWithMemberId:_mxRoomMember.userId completion:^{ + + [self removePendingActionMask]; + }]; } break; } From cab4ef5a24c861296fbb5cc5f0b22759b7419209 Mon Sep 17 00:00:00 2001 From: giomfo Date: Tue, 22 Mar 2016 10:26:10 +0100 Subject: [PATCH 05/30] MXKAccount: Fix invalid NSError instance. --- MatrixKit/Models/Account/MXKAccount.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MatrixKit/Models/Account/MXKAccount.m b/MatrixKit/Models/Account/MXKAccount.m index b54727606..8b87abc78 100644 --- a/MatrixKit/Models/Account/MXKAccount.m +++ b/MatrixKit/Models/Account/MXKAccount.m @@ -951,7 +951,7 @@ - (void)cancelBackgroundSync } } - [self onBackgroundSyncDone:[[NSError alloc] init]]; + [self onBackgroundSyncDone:[NSError errorWithDomain:kMXKAccountErrorDomain code:0 userInfo:nil]]; } } @@ -1053,7 +1053,7 @@ - (void)backgroundSync:(unsigned int)timeout success:(void (^)())success failure else { NSLog(@"[MXKAccount] cannot start background Sync (invalid state %tu)", mxSession.state); - failure([[NSError alloc] init]); + failure([NSError errorWithDomain:kMXKAccountErrorDomain code:0 userInfo:nil]); } } From 61a299c6855c6e635bb9d86a2b251b996cf0cdb9 Mon Sep 17 00:00:00 2001 From: giomfo Date: Wed, 23 Mar 2016 12:03:54 +0100 Subject: [PATCH 06/30] MXKAccount: Reset correctly the pending pause flag --- MatrixKit/Models/Account/MXKAccount.m | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/MatrixKit/Models/Account/MXKAccount.m b/MatrixKit/Models/Account/MXKAccount.m index 8b87abc78..d5c103103 100644 --- a/MatrixKit/Models/Account/MXKAccount.m +++ b/MatrixKit/Models/Account/MXKAccount.m @@ -290,12 +290,12 @@ - (void)setDisabled:(BOOL)disabled { // Close session (keep the storage). [self closeSession:NO]; - if (_enablePushNotifications) - { - // Turn off pusher - [self enablePusher:NO success:nil failure:nil]; - } - + if (_enablePushNotifications) + { + // Turn off pusher + [self enablePusher:NO success:nil failure:nil]; + } + } else if (!mxSession) { @@ -536,6 +536,9 @@ - (void)logout - (void)pauseInBackgroundTask { + // Reset internal flag + isPauseRequested = NO; + if (mxSession && mxSession.state == MXSessionStateRunning) { _bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ @@ -889,6 +892,10 @@ - (void)onMatrixSessionStateChange [[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountUserInfoDidChangeNotification object:mxCredentials.userId]; } } + else if (mxSession.state == MXSessionStatePaused) + { + isPauseRequested = NO; + } } - (void)prepareRESTClient @@ -1001,8 +1008,6 @@ - (void)onBackgroundSyncTimerOut - (void)backgroundSync:(unsigned int)timeout success:(void (^)())success failure:(void (^)(NSError *))failure { - isPauseRequested = NO; - // only work when the application is suspended // Check conditions before launching background sync From 1a9405731c87a932992bca4f241eba7cbf65a57a Mon Sep 17 00:00:00 2001 From: giomfo Date: Wed, 23 Mar 2016 14:38:09 +0100 Subject: [PATCH 07/30] MXKAccountManager: API change - [openSessionForActiveAccounts] is replaced by [prepareSessionForActiveAccounts]. This new method checks for each enabled account if a matrix session is already opened. It opens a matrix session for each enabled account which doesn't have a session. --- MatrixKit/Models/Account/MXKAccountManager.h | 8 +++++--- MatrixKit/Models/Account/MXKAccountManager.m | 10 +++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/MatrixKit/Models/Account/MXKAccountManager.h b/MatrixKit/Models/Account/MXKAccountManager.h index b5e7ecc40..85adf2828 100644 --- a/MatrixKit/Models/Account/MXKAccountManager.h +++ b/MatrixKit/Models/Account/MXKAccountManager.h @@ -75,10 +75,12 @@ extern NSString *const kMXKAccountManagerDidRemoveAccountNotification; + (MXKAccountManager*)sharedManager; /** - Open a matrix session for each enabled accounts. - The developper must set 'storeClass' before this call if the default class is not suitable. + Check for each enabled account if a matrix session is already opened. + Open a matrix session for each enabled account which doesn't have a session. + The developper must set 'storeClass' before the first call of this method + if the default class is not suitable. */ -- (void)openSessionForActiveAccounts; +- (void)prepareSessionForActiveAccounts; /** Save a snapshot of the current accounts. diff --git a/MatrixKit/Models/Account/MXKAccountManager.m b/MatrixKit/Models/Account/MXKAccountManager.m index 48db2f4f9..062ac3302 100644 --- a/MatrixKit/Models/Account/MXKAccountManager.m +++ b/MatrixKit/Models/Account/MXKAccountManager.m @@ -65,13 +65,15 @@ - (void)dealloc #pragma mark - -- (void)openSessionForActiveAccounts +- (void)prepareSessionForActiveAccounts { for (MXKAccount *account in mxAccounts) { - if (!account.isDisabled) + // Check whether the account is enabled. Open a new matrix session if none. + if (!account.isDisabled && !account.mxSession) { - // Open a new matrix session by default + NSLog(@"[MXKAccountManager] openSession for %@ account", account.mxCredentials.userId); + id store = [[_storeClass alloc] init]; [account openSessionWithStore:store]; } @@ -104,6 +106,8 @@ - (void)addAccount:(MXKAccount *)account andOpenSession:(BOOL)openSession if (openSession && !account.disabled) { // Open a new matrix session by default + NSLog(@"[MXKAccountManager] openSession for %@ account", account.mxCredentials.userId); + id store = [[_storeClass alloc] init]; [account openSessionWithStore:store]; } From 37cbccec9b4e852cac7d2386d18f4d6dafa7a384 Mon Sep 17 00:00:00 2001 From: giomfo Date: Wed, 23 Mar 2016 14:50:29 +0100 Subject: [PATCH 08/30] Fix compilation error in matrixKit sample --- Samples/MatrixKitSample/MXKSampleMainTableViewController.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Samples/MatrixKitSample/MXKSampleMainTableViewController.m b/Samples/MatrixKitSample/MXKSampleMainTableViewController.m index 4657843db..f20f77d7b 100644 --- a/Samples/MatrixKitSample/MXKSampleMainTableViewController.m +++ b/Samples/MatrixKitSample/MXKSampleMainTableViewController.m @@ -185,7 +185,7 @@ - (void)viewDidLoad // Observers have been set, we will start now a matrix session for each enabled accounts. // As there is no mock for MatrixSDK yet, use an actual Matrix file store to boost init [MXKAccountManager sharedManager].storeClass = [MXFileStore class]; - [[MXKAccountManager sharedManager] openSessionForActiveAccounts]; + [[MXKAccountManager sharedManager] prepareSessionForActiveAccounts]; // Check whether some accounts are availables if (![[MXKAccountManager sharedManager] accounts].count) @@ -880,14 +880,14 @@ - (void)contactListViewController:(MXKContactListViewController *)contactListVie #pragma mark - MXKRoomMemberDetailsViewControllerDelegate -- (void)roomMemberDetailsViewController:(MXKRoomMemberDetailsViewController *)roomMemberDetailsViewController startChatWithMemberId:(NSString *)matrixId +- (void)roomMemberDetailsViewController:(MXKRoomMemberDetailsViewController *)roomMemberDetailsViewController startChatWithMemberId:(NSString *)matrixId completion:(void (^)(void))completion { NSLog(@" -> Start chat with %@ is requested", matrixId); } #pragma mark - MXKContactDetailsViewControllerDelegate -- (void)contactDetailsViewController:(MXKContactDetailsViewController *)contactDetailsViewController startChatWithMatrixId:(NSString *)matrixId +- (void)contactDetailsViewController:(MXKContactDetailsViewController *)contactDetailsViewController startChatWithMatrixId:(NSString *)matrixId completion:(void (^)(void))completion { NSLog(@" -> Start chat with %@ is requested", matrixId); } From 19eaa198b0541a0aafac8da854d3fa106013cd52 Mon Sep 17 00:00:00 2001 From: giomfo Date: Thu, 24 Mar 2016 11:56:14 +0100 Subject: [PATCH 09/30] MXKEventFormatter: remove useless trace --- MatrixKit/Utils/MXKEventFormatter.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MatrixKit/Utils/MXKEventFormatter.m b/MatrixKit/Utils/MXKEventFormatter.m index 2f9810737..df2c557b7 100644 --- a/MatrixKit/Utils/MXKEventFormatter.m +++ b/MatrixKit/Utils/MXKEventFormatter.m @@ -183,10 +183,11 @@ - (NSString*)stringFromEvent:(MXEvent*)event withRoomState:(MXRoomState*)roomSta BOOL isRedacted = (event.redactedBecause != nil); if (isRedacted) { - NSLog(@"[MXKEventFormatter] Redacted event %@ (%@)", event.description, event.redactedBecause); // Check whether redacted information is required if (_settings.showRedactionsInRoomHistory) { + NSLog(@"[MXKEventFormatter] Redacted event %@ (%@)", event.description, event.redactedBecause); + NSString *redactorId = event.redactedBecause[@"sender"]; NSString *redactedBy = @""; // Consider live room state to resolve redactor name if no roomState is provided From 88a8c7f2842cf816eab6361ce03fa030240da851 Mon Sep 17 00:00:00 2001 From: giomfo Date: Tue, 29 Mar 2016 15:28:11 +0200 Subject: [PATCH 10/30] Registration Support - Update existing classes to support authentication session. + Add MXKAuthenticationRecaptchaWebView class (Not tested yet). --- MatrixKit.xcodeproj/project.pbxproj | 6 + .../MXKAuthenticationViewController.h | 60 +- .../MXKAuthenticationViewController.m | 659 ++++++++---------- MatrixKit/MatrixKit.h | 2 + .../MXKAuthInputsEmailCodeBasedView.m | 98 ++- .../MXKAuthInputsPasswordBasedView.m | 96 ++- .../Views/Authentication/MXKAuthInputsView.h | 63 +- .../Views/Authentication/MXKAuthInputsView.m | 46 +- .../MXKAuthenticationRecaptchaWebView.h | 30 + .../MXKAuthenticationRecaptchaWebView.m | 129 ++++ 10 files changed, 710 insertions(+), 479 deletions(-) create mode 100644 MatrixKit/Views/Authentication/MXKAuthenticationRecaptchaWebView.h create mode 100644 MatrixKit/Views/Authentication/MXKAuthenticationRecaptchaWebView.m diff --git a/MatrixKit.xcodeproj/project.pbxproj b/MatrixKit.xcodeproj/project.pbxproj index c6ddf7906..5775089a9 100644 --- a/MatrixKit.xcodeproj/project.pbxproj +++ b/MatrixKit.xcodeproj/project.pbxproj @@ -61,6 +61,7 @@ F00FA8781C08A51B00E25826 /* MXKRoomOutgoingTextMsgBubbleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F00FA86F1C08A51B00E25826 /* MXKRoomOutgoingTextMsgBubbleCell.xib */; }; F00FA8791C08A51B00E25826 /* MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = F00FA8711C08A51B00E25826 /* MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m */; }; F00FA87A1C08A51B00E25826 /* MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F00FA8721C08A51B00E25826 /* MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib */; }; + F011FA681CA3F43C009EA677 /* MXKAuthenticationRecaptchaWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = F011FA671CA3F43C009EA677 /* MXKAuthenticationRecaptchaWebView.m */; }; F012674A1AF8A9440067D962 /* MXK3PID.m in Sources */ = {isa = PBXBuildFile; fileRef = F01267491AF8A9440067D962 /* MXK3PID.m */; }; F01449AB1B53FA7600EA7D73 /* MXKPushRuleTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = F01449A61B53FA7600EA7D73 /* MXKPushRuleTableViewCell.m */; }; F01449AC1B53FA7600EA7D73 /* MXKPushRuleTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F01449A71B53FA7600EA7D73 /* MXKPushRuleTableViewCell.xib */; }; @@ -300,6 +301,8 @@ F00FA8701C08A51B00E25826 /* MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.h; sourceTree = ""; }; F00FA8711C08A51B00E25826 /* MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m; sourceTree = ""; }; F00FA8721C08A51B00E25826 /* MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib; sourceTree = ""; }; + F011FA661CA3F43C009EA677 /* MXKAuthenticationRecaptchaWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXKAuthenticationRecaptchaWebView.h; sourceTree = ""; }; + F011FA671CA3F43C009EA677 /* MXKAuthenticationRecaptchaWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXKAuthenticationRecaptchaWebView.m; sourceTree = ""; }; F01267481AF8A9440067D962 /* MXK3PID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXK3PID.h; sourceTree = ""; }; F01267491AF8A9440067D962 /* MXK3PID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXK3PID.m; sourceTree = ""; }; F01449A51B53FA7600EA7D73 /* MXKPushRuleTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXKPushRuleTableViewCell.h; sourceTree = ""; }; @@ -905,6 +908,8 @@ F06E76921AF0FED000980E5A /* MXKAuthInputsView.m */, F06E76931AF0FED000980E5A /* MXKAuthenticationFallbackWebView.h */, F06E76941AF0FED000980E5A /* MXKAuthenticationFallbackWebView.m */, + F011FA661CA3F43C009EA677 /* MXKAuthenticationRecaptchaWebView.h */, + F011FA671CA3F43C009EA677 /* MXKAuthenticationRecaptchaWebView.m */, ); path = Authentication; sourceTree = ""; @@ -1440,6 +1445,7 @@ 328AC48C1AA760520044A6FB /* MXKRoomOutgoingBubbleTableViewCell.m in Sources */, 32CEE21F1AB1A57E00F7C74D /* MXKEventFormatter.m in Sources */, 3230A3751ACADC1800CC57F5 /* MXKRoomDataSourceManager.m in Sources */, + F011FA681CA3F43C009EA677 /* MXKAuthenticationRecaptchaWebView.m in Sources */, F0CF98E11B0B7FDC00EAE373 /* MXKTableViewCellWithLabelAndSwitch.m in Sources */, F02528961C1092D300E1FE1B /* MXKCollectionViewCell.m in Sources */, F0F148A61AB08F48005F5D4A /* MXKSampleJSQRoomDataSource.m in Sources */, diff --git a/MatrixKit/Controllers/MXKAuthenticationViewController.h b/MatrixKit/Controllers/MXKAuthenticationViewController.h index 0bbd8ad44..00b629e34 100644 --- a/MatrixKit/Controllers/MXKAuthenticationViewController.h +++ b/MatrixKit/Controllers/MXKAuthenticationViewController.h @@ -48,18 +48,6 @@ extern NSString *const MXKAuthErrorDomain; You may add a delegate to be notified when a new account has been added successfully. */ @interface MXKAuthenticationViewController : MXKViewController -{ -@protected - /** - Array of flows supported by the home server and implemented by the view controller (for the current auth type). - */ - NSMutableArray *supportedFlows; - - /** - The current selected login flow - */ - MXLoginFlow *selectedFlow; -} @property (weak, nonatomic) IBOutlet UIImageView *welcomeImageView; @@ -99,9 +87,9 @@ extern NSString *const MXKAuthErrorDomain; @property (nonatomic) MXKAuthenticationType authType; /** - The current selected login flow + The current view in which authentication inputs are displayed (`MXKAuthInputsView-inherited` instance). */ -@property (nonatomic) MXLoginFlow *selectedFlow; +@property (nonatomic) MXKAuthInputsView *authInputsView; /** The default home server url (nil by default). @@ -119,46 +107,46 @@ extern NSString *const MXKAuthErrorDomain; @property (nonatomic) id delegate; /** - * Returns the `UINib` object initialized for a `MXKAuthenticationViewController`. - * - * @return The initialized `UINib` object or `nil` if there were errors during initialization - * or the nib file could not be located. - * - * @discussion You may override this method to provide a customized nib. If you do, - * you should also override `authenticationViewController` to return your - * view controller loaded from your custom nib. + Returns the `UINib` object initialized for a `MXKAuthenticationViewController`. + + @return The initialized `UINib` object or `nil` if there were errors during initialization + or the nib file could not be located. + + @discussion You may override this method to provide a customized nib. If you do, + you should also override `authenticationViewController` to return your + view controller loaded from your custom nib. */ + (UINib *)nib; /** - * Creates and returns a new `MXKAuthenticationViewController` object. - * - * @discussion This is the designated initializer for programmatic instantiation. - * - * @return An initialized `MXKAuthenticationViewController` object if successful, `nil` otherwise. + Creates and returns a new `MXKAuthenticationViewController` object. + + @discussion This is the designated initializer for programmatic instantiation. + + @return An initialized `MXKAuthenticationViewController` object if successful, `nil` otherwise. */ + (instancetype)authenticationViewController; /** - Register the MXKAuthInputsView class that will be used to display inputs for the designated flow and authentication type. + Register the MXKAuthInputsView class that will be used to display inputs for an authentication type. - By default only 'MXKAuthInputsPasswordBasedView' class is registered for 'kMXLoginFlowTypePassword' flow and 'MXKAuthenticationTypeLogin' authentication. + By default the 'MXKAuthInputsPasswordBasedView' class is registered for 'MXKAuthenticationTypeLogin' authentication. + No class is registered for 'MXKAuthenticationTypeRegister' type. - @param authInputsViewClass a MXKAuthInputsView-inherited class that will be used for the designated flow. - @param flowType the concerned flow type. + @param authInputsViewClass a MXKAuthInputsView-inherited class. @param authType the concerned authentication type */ -- (void)registerAuthInputsViewClass:(Class)authInputsViewClass forFlowType:(MXLoginFlowType)flowType andAuthType:(MXKAuthenticationType)authType; +- (void)registerAuthInputsViewClass:(Class)authInputsViewClass forAuthType:(MXKAuthenticationType)authType; /** - Check login mechanism supported by the server and the application. + Refresh login/register mechanism supported by the server and the application. */ -- (void)refreshSupportedAuthFlow; +- (void)refreshAuthenticationSession; /** - Handle supported flows returned by the server. + Handle supported flows and associated information returned by the home server. */ -- (void)handleHomeServerFlows:(NSArray *)flows; +- (void)handleAuthenticationSession:(MXAuthenticationSession *)authSession; /** Customize the MXHTTPClientOnUnrecognizedCertificate block that will be used to handle unrecognized certificate observed during authentication challenge from a server. diff --git a/MatrixKit/Controllers/MXKAuthenticationViewController.m b/MatrixKit/Controllers/MXKAuthenticationViewController.m index 239402827..10150a307 100644 --- a/MatrixKit/Controllers/MXKAuthenticationViewController.m +++ b/MatrixKit/Controllers/MXKAuthenticationViewController.m @@ -40,25 +40,20 @@ @interface MXKAuthenticationViewController () */ MXHTTPOperation *mxCurrentOperation; - /** - The current view in which auth inputs are displayed (`MXKAuthInputsView-inherited` instance). - */ - MXKAuthInputsView *currentAuthInputsView; - /** Reference to any opened alert view. */ MXKAlert *alert; /** - The mapping between flow type and MXKAuthInputsView classes used when logging in. + The MXKAuthInputsView class or a sub-class used when logging in. */ - NSMutableDictionary *loginAuthInputsViewMap; + Class loginAuthInputsViewClass; /** - The mapping between flow type and MXKAuthInputsView classes used when registering. + The MXKAuthInputsView class or a sub-class used when registering. */ - NSMutableDictionary *registerAuthInputsViewMap; + Class registerAuthInputsViewClass; /** Customized block used to handle unrecognized certificate (nil by default). @@ -79,7 +74,6 @@ The current authentication fallback URL (if any). @end @implementation MXKAuthenticationViewController -@synthesize selectedFlow; #pragma mark - Class methods @@ -164,7 +158,6 @@ - (void)viewDidLoad _submitButton.enabled = NO; _authSwitchButton.enabled = YES; - supportedFlows = [NSMutableArray array]; _homeServerTextField.text = _defaultHomeServerUrl; _identityServerTextField.text = _defaultIdentityServerUrl; @@ -186,13 +179,9 @@ - (void)viewDidLoad // Set initial auth type _authType = MXKAuthenticationTypeLogin; - // Initialize supported flows by defining authInputs views - loginAuthInputsViewMap = [NSMutableDictionary dictionary]; - loginAuthInputsViewMap[kMXLoginFlowTypePassword] = MXKAuthInputsPasswordBasedView.class; -// loginAuthInputsViewMap[kMXLoginFlowTypeEmailCode] = MXKAuthInputsEmailCodeBasedView.class; - - registerAuthInputsViewMap = [NSMutableDictionary dictionary]; - // No registration flow is supported yet + // Initialize authInputs view classes + loginAuthInputsViewClass = MXKAuthInputsPasswordBasedView.class; + registerAuthInputsViewClass = nil; // No registration flow is supported yet } - (void)dealloc @@ -258,7 +247,8 @@ - (void)setKeyboardHeight:(CGFloat)keyboardHeight - (void)destroy { - supportedFlows = nil; + self.authInputsView = nil; + if (mxCurrentOperation){ [mxCurrentOperation cancel]; mxCurrentOperation = nil; @@ -266,9 +256,6 @@ - (void)destroy [mxRestClient close]; mxRestClient = nil; - - loginAuthInputsViewMap = nil; - registerAuthInputsViewMap = nil; authenticationFallback = nil; cancelFallbackBarButton = nil; @@ -278,21 +265,18 @@ - (void)destroy #pragma mark - Class methods -- (void)registerAuthInputsViewClass:(Class)authInputsViewClass forFlowType:(MXLoginFlowType)flowType andAuthType:(MXKAuthenticationType)authType +- (void)registerAuthInputsViewClass:(Class)authInputsViewClass forAuthType:(MXKAuthenticationType)authType { - if (flowType) + // Sanity check: accept only MXKAuthInputsView classes or sub-classes + NSParameterAssert([authInputsViewClass isSubclassOfClass:MXKAuthInputsView.class]); + + if (authType == MXKAuthenticationTypeLogin) { - // Sanity check: accept only MXKAuthInputsView classes or sub-classes - NSParameterAssert([authInputsViewClass isSubclassOfClass:MXKAuthInputsView.class]); - - if (authType == MXKAuthenticationTypeLogin) - { - loginAuthInputsViewMap[flowType] = authInputsViewClass; - } - else - { - registerAuthInputsViewMap[flowType] = authInputsViewClass; - } + loginAuthInputsViewClass = authInputsViewClass; + } + else + { + registerAuthInputsViewClass = authInputsViewClass; } } @@ -317,8 +301,100 @@ - (void)setAuthType:(MXKAuthenticationType)authType _authType = authType; - // Update supported authentication flow - [self refreshSupportedAuthFlow]; + // Update supported authentication flow and associated information (defined in authentication session) + [self refreshAuthenticationSession]; +} + +- (void)setAuthInputsView:(MXKAuthInputsView *)authInputsView +{ + // Here a new view will be loaded, hide first subviews which depend on auth flow + _submitButton.hidden = YES; + _noFlowLabel.hidden = YES; + _retryButton.hidden = YES; + + if (_authInputsView) + { + [_authInputsView removeObserver:self forKeyPath:@"actualHeight"]; + + if ([NSLayoutConstraint respondsToSelector:@selector(deactivateConstraints:)]) + { + [NSLayoutConstraint deactivateConstraints:_authInputsView.constraints]; + } + else + { + [_authInputsContainerView removeConstraints:_authInputsView.constraints]; + } + + [_authInputsView removeFromSuperview]; + _authInputsView.delegate = nil; + _authInputsView = nil; + } + + _authInputsView = authInputsView; + + CGFloat previousInputsContainerViewHeight = _authInputContainerViewHeightConstraint.constant; + + if (_authInputsView) + { + _authInputsView.translatesAutoresizingMaskIntoConstraints = NO; // FIXME GFO useful or not? + [_authInputsContainerView addSubview:_authInputsView]; + + _authInputsView.delegate = self; + + _submitButton.hidden = NO; + _authInputsView.hidden = NO; + + _authInputContainerViewHeightConstraint.constant = _authInputsView.actualHeight; + + NSLayoutConstraint* topConstraint = [NSLayoutConstraint constraintWithItem:_authInputsContainerView + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:_authInputsView + attribute:NSLayoutAttributeTop + multiplier:1.0f + constant:0.0f]; + + + NSLayoutConstraint* leadingConstraint = [NSLayoutConstraint constraintWithItem:_authInputsContainerView + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:_authInputsView + attribute:NSLayoutAttributeLeading + multiplier:1.0f + constant:0.0f]; + + NSLayoutConstraint* trailingConstraint = [NSLayoutConstraint constraintWithItem:_authInputsContainerView + attribute:NSLayoutAttributeTrailing + relatedBy:NSLayoutRelationEqual + toItem:_authInputsView + attribute:NSLayoutAttributeTrailing + multiplier:1.0f + constant:0.0f]; + + + if ([NSLayoutConstraint respondsToSelector:@selector(activateConstraints:)]) + { + [NSLayoutConstraint activateConstraints:@[topConstraint, leadingConstraint, trailingConstraint]]; + } + else + { + [_authInputsContainerView addConstraint:topConstraint]; + [_authInputsContainerView addConstraint:leadingConstraint]; + [_authInputsContainerView addConstraint:trailingConstraint]; + } + + [_authInputsView addObserver:self forKeyPath:@"actualHeight" options:0 context:nil]; + } + else + { + // No input fields are displayed + _authInputContainerViewHeightConstraint.constant = _authInputContainerViewMinHeightConstraint.constant; + } + + [self.view layoutIfNeeded]; + + // Refresh content view height by considering the updated height of inputs container + _contentViewHeightConstraint.constant += (_authInputContainerViewHeightConstraint.constant - previousInputsContainerViewHeight); } - (void)setDefaultHomeServerUrl:(NSString *)defaultHomeServerUrl @@ -347,7 +423,7 @@ - (void)setDefaultIdentityServerUrl:(NSString *)defaultIdentityServerUrl } } -- (void)refreshSupportedAuthFlow +- (void)refreshAuthenticationSession { // Remove reachability observer [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNetworkingReachabilityDidChangeNotification object:nil]; @@ -365,10 +441,9 @@ - (void)refreshSupportedAuthFlow { if (_authType == MXKAuthenticationTypeLogin) { - mxCurrentOperation = [mxRestClient getLoginFlow:^(NSDictionary *JSONResponse) { + mxCurrentOperation = [mxRestClient getLoginSession:^(MXAuthenticationSession* authSession) { - NSArray *flows = [MXLoginFlow modelsFromJSON:JSONResponse[@"flows"]]; - [self handleHomeServerFlows:flows]; + [self handleAuthenticationSession:authSession]; } failure:^(NSError *error) { @@ -379,10 +454,9 @@ - (void)refreshSupportedAuthFlow } else { - mxCurrentOperation = [mxRestClient getRegisterFlow:^(NSDictionary *JSONResponse){ + mxCurrentOperation = [mxRestClient getRegisterSession:^(MXAuthenticationSession* authSession){ - NSArray *flows = [MXLoginFlow modelsFromJSON:JSONResponse[@"flows"]]; - [self handleHomeServerFlows:flows]; + [self handleAuthenticationSession:authSession]; } failure:^(NSError *error){ @@ -394,11 +468,165 @@ - (void)refreshSupportedAuthFlow } } +- (void)handleAuthenticationSession:(MXAuthenticationSession *)authSession +{ + [_authenticationActivityIndicator stopAnimating]; + mxCurrentOperation = nil; + + // Check whether fallback is defined, and instantiate an auth inputs view (if a class is defined). + MXKAuthInputsView *authInputsView; + if (_authType == MXKAuthenticationTypeLogin) + { + authenticationFallback = [mxRestClient loginFallback]; + + if (loginAuthInputsViewClass) + { + authInputsView = [loginAuthInputsViewClass authInputsView]; + } + } + else + { + authenticationFallback = [mxRestClient registerFallback]; + + if (registerAuthInputsViewClass) + { + authInputsView = [registerAuthInputsViewClass authInputsView]; + } + } + + if (authInputsView) + { + // Apply authentication session on inputs view + if ([authInputsView setAuthSession:authSession withAuthType:_authType]) + { + NSLog(@"[MXKAuthenticationVC] Received authentication settings are not supported"); + authInputsView = nil; + } + // Check whether all listed flows in this authentication session are supported + // We suggest using the fallback page (if any), when at least one flow is not supported. + else if ((authInputsView.authSession.flows.count != authSession.flows.count) && authenticationFallback.length) + { + NSLog(@"[MXKAuthenticationVC] Suggest using fallback page"); + authInputsView = nil; + } + } + + if (authInputsView) + { + // Refresh UI + self.authInputsView = authInputsView; + } + else + { + // Remove the potential auth inputs view + self.authInputsView = nil; + + // Notify user that no flow is supported + if (_authType == MXKAuthenticationTypeLogin) + { + _noFlowLabel.text = [NSBundle mxk_localizedStringForKey:@"login_error_do_not_support_login_flows"]; + } + else + { + _noFlowLabel.text = [NSBundle mxk_localizedStringForKey:@"login_error_registration_is_not_supported"]; + } + NSLog(@"[MXKAuthenticationVC] Warning: %@", _noFlowLabel.text); + + if (authenticationFallback.length) + { + [_retryButton setTitle:[NSBundle mxk_localizedStringForKey:@"login_use_fallback"] forState:UIControlStateNormal]; + [_retryButton setTitle:[NSBundle mxk_localizedStringForKey:@"login_use_fallback"] forState:UIControlStateNormal]; + } + else + { + [_retryButton setTitle:[NSBundle mxk_localizedStringForKey:@"retry"] forState:UIControlStateNormal]; + [_retryButton setTitle:[NSBundle mxk_localizedStringForKey:@"retry"] forState:UIControlStateNormal]; + } + + _noFlowLabel.hidden = NO; + _retryButton.hidden = NO; + } +} + - (void)setOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertificateBlock { onUnrecognizedCertificateCustomBlock = onUnrecognizedCertificateBlock; } +- (IBAction)onButtonPressed:(id)sender +{ + [self dismissKeyboard]; + + if (sender == _submitButton) + { + if (mxRestClient) + { + // Disable user interaction to prevent multiple requests + [self setUserInteractionEnabled:NO]; + [self.authInputsContainerView bringSubviewToFront: _authenticationActivityIndicator]; + [_authenticationActivityIndicator startAnimating]; + + if (_authType == MXKAuthenticationTypeLogin) + { + // if ([selectedStages indexOfObject:kMXLoginFlowTypePassword] != NSNotFound) FIXME GFO + { + MXKAuthInputsPasswordBasedView *authInputsView = (MXKAuthInputsPasswordBasedView*)_authInputsView; + + NSString *user = authInputsView.userLoginTextField.text; + user = [user stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + + [mxRestClient loginWithUser:user andPassword:authInputsView.passWordTextField.text + success:^(MXCredentials *credentials){ + [_authenticationActivityIndicator stopAnimating]; + + [self onSuccessfulLogin:credentials]; + } + failure:^(NSError *error){ + [self onFailureDuringAuthRequest:error]; + }]; + } + // else + // { + // // FIXME GFO + // [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; + // } + } + else + { + // FIXME + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; + } + } + } + else if (sender == _authSwitchButton) + { + if (_authType == MXKAuthenticationTypeLogin) + { + self.authType = MXKAuthenticationTypeRegister; + } + else + { + self.authType = MXKAuthenticationTypeLogin; + } + } + else if (sender == _retryButton) + { + if (authenticationFallback) + { + [self showAuthenticationFallBackView:authenticationFallback]; + } + else + { + [self refreshAuthenticationSession]; + } + } + else if (sender == _cancelAuthFallbackButton) + { + // Hide fallback webview + [self hideRegistrationFallbackView]; + } +} + #pragma mark - Privates - (void)updateRESTClient @@ -477,248 +705,13 @@ - (void)updateRESTClient - (void)setUserInteractionEnabled:(BOOL)isEnabled { - _submitButton.enabled = (isEnabled && currentAuthInputsView.areAllRequiredFieldsFilled); + _submitButton.enabled = (isEnabled && _authInputsView.areAllRequiredFieldsFilled); _authSwitchButton.enabled = isEnabled; _homeServerTextField.enabled = isEnabled; _identityServerTextField.enabled = isEnabled; } -- (BOOL)isImplementedFlowType:(MXLoginFlowType)flowType forAuthType:(MXKAuthenticationType)authType -{ - // Sanity check - if (flowType) - { - NSDictionary *authInputsViewMap; - - if (authType == MXKAuthenticationTypeLogin) - { - authInputsViewMap = loginAuthInputsViewMap; - } - else - { - authInputsViewMap = registerAuthInputsViewMap; - } - - // A flow is supported only if an authInputsView class is registered. - return (authInputsViewMap[flowType] != nil); - } - - return NO; -} - -- (void)handleHomeServerFlows:(NSArray *)flows -{ - [_authenticationActivityIndicator stopAnimating]; - mxCurrentOperation = nil; - - // Check whether fallback is defined - if (_authType == MXKAuthenticationTypeLogin) - { - authenticationFallback = [mxRestClient loginFallback]; - } - else - { - authenticationFallback = [mxRestClient registerFallback]; - } - - // List supported flows - [supportedFlows removeAllObjects]; - - for (MXLoginFlow* flow in flows) - { - // Check whether flow type is defined (this type has been deprecated since C-S API v2) - if (flow.type) - { - if ([self isImplementedFlowType:flow.type forAuthType:_authType]) - { - // Check here all stages - BOOL isSupported = YES; - if (flow.stages.count) - { - for (NSString *stage in flow.stages) - { - if ([self isImplementedFlowType:stage forAuthType:_authType] == NO) - { - NSLog(@"[MXKAuthenticationVC] %@: %@ stage is not supported.", (_authType == MXKAuthenticationTypeLogin ? @"login" : @"register"), stage); - isSupported = NO; - break; - } - } - } - - if (isSupported) - { - [supportedFlows addObject:flow]; - } - } - else - { - NSLog(@"[MXKAuthenticationVC] %@: %@ stage is not supported.", (_authType == MXKAuthenticationTypeLogin ? @"login" : @"register"), flow.type); - } - } - else - { - // Check here all stages - BOOL isSupported = YES; - if (flow.stages.count) - { - for (NSString *stage in flow.stages) - { - if ([self isImplementedFlowType:stage forAuthType:_authType] == NO) - { - NSLog(@"[MXKAuthenticationVC] %@: %@ stage is not supported.", (_authType == MXKAuthenticationTypeLogin ? @"login" : @"register"), stage); - isSupported = NO; - break; - } - } - } - - if (isSupported) - { - [supportedFlows addObject:flow]; - } - } - } - - // We will suggest using the fallback page (if any), when at least one flow is not supported. - if ((supportedFlows.count != flows.count) && authenticationFallback.length) - { - NSLog(@"[MXKAuthenticationVC] Suggest using fallback page"); - - // Remove the potential auth inputs view - self.selectedFlow = nil; - } - else if (supportedFlows.count) - { - // FIXME display supported flows - // Currently we select the first one - self.selectedFlow = [supportedFlows firstObject]; - } - else - { - // Remove the potential auth inputs view - self.selectedFlow = nil; - } - - if (!self.selectedFlow) - { - // Notify user that no flow is supported - if (_authType == MXKAuthenticationTypeLogin) - { - _noFlowLabel.text = [NSBundle mxk_localizedStringForKey:@"login_error_do_not_support_login_flows"]; - } - else - { - _noFlowLabel.text = [NSBundle mxk_localizedStringForKey:@"login_error_registration_is_not_supported"]; - } - NSLog(@"[MXKAuthenticationVC] Warning: %@", _noFlowLabel.text); - - if (authenticationFallback.length) - { - [_retryButton setTitle:[NSBundle mxk_localizedStringForKey:@"login_use_fallback"] forState:UIControlStateNormal]; - [_retryButton setTitle:[NSBundle mxk_localizedStringForKey:@"login_use_fallback"] forState:UIControlStateNormal]; - } - else - { - [_retryButton setTitle:[NSBundle mxk_localizedStringForKey:@"retry"] forState:UIControlStateNormal]; - [_retryButton setTitle:[NSBundle mxk_localizedStringForKey:@"retry"] forState:UIControlStateNormal]; - } - - _noFlowLabel.hidden = NO; - _retryButton.hidden = NO; - } -} - -- (void)setSelectedFlow:(MXLoginFlow *)inSelectedFlow -{ - selectedFlow = inSelectedFlow; - - // C-S API v2: Consider the first flow from stages as current type - if (selectedFlow.type == nil && selectedFlow.stages.count) - { - selectedFlow.type = selectedFlow.stages.firstObject; - } - - // Retrieve the corresponding auth inputs view - NSDictionary *authInputsViewMap; - if (self.authType == MXKAuthenticationTypeLogin) - { - authInputsViewMap = loginAuthInputsViewMap; - } - else - { - authInputsViewMap = registerAuthInputsViewMap; - } - - Class class = authInputsViewMap[selectedFlow.type]; - - // Keep the current view if it is still relevant - if (currentAuthInputsView && [currentAuthInputsView isKindOfClass:class]) - { - return; - } - - // Here a new view will be loaded, hide first subviews which depend on auth flow - _submitButton.hidden = YES; - _noFlowLabel.hidden = YES; - _retryButton.hidden = YES; - - [currentAuthInputsView removeFromSuperview]; - currentAuthInputsView.delegate = nil; - currentAuthInputsView = nil; - - if (class) - { - currentAuthInputsView = [class authInputsView]; - } - - CGFloat previousInputsContainerViewHeight = _authInputContainerViewHeightConstraint.constant; - - if (currentAuthInputsView) - { - [_authInputsContainerView addSubview:currentAuthInputsView]; - - currentAuthInputsView.delegate = self; - _submitButton.hidden = NO; - currentAuthInputsView.hidden = NO; - currentAuthInputsView.authType = _authType; - _authInputContainerViewHeightConstraint.constant = currentAuthInputsView.actualHeight; - - [_authInputsContainerView addConstraint:[NSLayoutConstraint constraintWithItem:_authInputsContainerView - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:currentAuthInputsView - attribute:NSLayoutAttributeTop - multiplier:1.0f - constant:0.0f]]; - [_authInputsContainerView addConstraint:[NSLayoutConstraint constraintWithItem:_authInputsContainerView - attribute:NSLayoutAttributeLeading - relatedBy:NSLayoutRelationEqual - toItem:currentAuthInputsView - attribute:NSLayoutAttributeLeading - multiplier:1.0f - constant:0.0f]]; - [_authInputsContainerView addConstraint:[NSLayoutConstraint constraintWithItem:_authInputsContainerView - attribute:NSLayoutAttributeTrailing - relatedBy:NSLayoutRelationEqual - toItem:currentAuthInputsView - attribute:NSLayoutAttributeTrailing - multiplier:1.0f - constant:0.0f]]; - } - else - { - // No input fields are displayed - _authInputContainerViewHeightConstraint.constant = _authInputContainerViewMinHeightConstraint.constant; - } - - [self.view layoutIfNeeded]; - - // Refresh content view height by considering the updated height of inputs container - _contentViewHeightConstraint.constant += (_authInputContainerViewHeightConstraint.constant - previousInputsContainerViewHeight); -} - - (void)onFailureDuringMXOperation:(NSError*)error { mxCurrentOperation = nil; @@ -757,22 +750,22 @@ - (void)onFailureDuringMXOperation:(NSError*)error { // Send a new request in 2 sec dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self refreshSupportedAuthFlow]; + [self refreshAuthenticationSession]; }); } else { // Remove the potential auth inputs view - self.selectedFlow = nil; + self.authInputsView = nil; } } else { // Remove the potential auth inputs view - self.selectedFlow = nil; + self.authInputsView = nil; } - if (!selectedFlow) + if (!_authInputsView) { // Display failure reason _noFlowLabel.hidden = NO; @@ -795,7 +788,7 @@ - (void)onReachabilityStatusChange:(NSNotification *)notif if (status == AFNetworkReachabilityStatusReachableViaWiFi || status == AFNetworkReachabilityStatusReachableViaWWAN) { dispatch_async(dispatch_get_main_queue(), ^{ - [self refreshSupportedAuthFlow]; + [self refreshAuthenticationSession]; }); } else if (status == AFNetworkReachabilityStatusNotReachable) @@ -804,80 +797,6 @@ - (void)onReachabilityStatusChange:(NSNotification *)notif } } -- (IBAction)onButtonPressed:(id)sender -{ - [self dismissKeyboard]; - - if (sender == _submitButton) - { - if (mxRestClient) - { - // Disable user interaction to prevent multiple requests - [self setUserInteractionEnabled:NO]; - [self.authInputsContainerView bringSubviewToFront: _authenticationActivityIndicator]; - [_authenticationActivityIndicator startAnimating]; - - if (_authType == MXKAuthenticationTypeLogin) - { - if ([self.selectedFlow.type isEqualToString:kMXLoginFlowTypePassword]) - { - MXKAuthInputsPasswordBasedView *authInputsView = (MXKAuthInputsPasswordBasedView*)currentAuthInputsView; - - NSString *user = authInputsView.userLoginTextField.text; - user = [user stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - - [mxRestClient loginWithUser:user andPassword:authInputsView.passWordTextField.text - success:^(MXCredentials *credentials){ - [_authenticationActivityIndicator stopAnimating]; - - [self onSuccessfulLogin:credentials]; - } - failure:^(NSError *error){ - [self onFailureDuringAuthRequest:error]; - }]; - } - else - { - // FIXME - [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; - } - } - else - { - // FIXME - [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; - } - } - } - else if (sender == _authSwitchButton) - { - if (_authType == MXKAuthenticationTypeLogin) - { - self.authType = MXKAuthenticationTypeRegister; - } - else - { - self.authType = MXKAuthenticationTypeLogin; - } - } - else if (sender == _retryButton) - { - if (authenticationFallback) - { - [self showAuthenticationFallBackView:authenticationFallback]; - } - else - { - [self refreshSupportedAuthFlow]; - } - } - else if (sender == _cancelAuthFallbackButton) - { - // Hide fallback webview - [self hideRegistrationFallbackView]; - } -} - - (void)onFailureDuringAuthRequest:(NSError *)error { [_authenticationActivityIndicator stopAnimating]; @@ -977,7 +896,7 @@ - (void)onSuccessfulLogin:(MXCredentials*)credentials - (void)dismissKeyboard { // Hide the keyboard - [currentAuthInputsView dismissKeyboard]; + [_authInputsView dismissKeyboard]; [_homeServerTextField resignFirstResponder]; [_identityServerTextField resignFirstResponder]; } @@ -986,7 +905,7 @@ - (void)dismissKeyboard - (void)onTextFieldChange:(NSNotification *)notif { - _submitButton.enabled = currentAuthInputsView.areAllRequiredFieldsFilled; + _submitButton.enabled = _authInputsView.areAllRequiredFieldsFilled; } - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField @@ -1022,7 +941,7 @@ - (void)textFieldDidEndEditing:(UITextField *)textField [self updateRESTClient]; // Refresh UI - [self refreshSupportedAuthFlow]; + [self refreshAuthenticationSession]; } else if (textField == _identityServerTextField) { @@ -1106,4 +1025,20 @@ - (void)hideRegistrationFallbackView _authFallbackContentView.hidden = YES; } +#pragma mark - KVO + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if ([@"actualHeight" isEqualToString:keyPath]) + { + // Refresh the height of the auth inputs view container. + _authInputContainerViewHeightConstraint.constant = _authInputsView.actualHeight; + [self.view setNeedsUpdateConstraints]; + } + else + { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + @end diff --git a/MatrixKit/MatrixKit.h b/MatrixKit/MatrixKit.h index d3cf004c6..8b9f4e554 100644 --- a/MatrixKit/MatrixKit.h +++ b/MatrixKit/MatrixKit.h @@ -47,6 +47,8 @@ #import "MXKAuthenticationViewController.h" #import "MXKAuthInputsPasswordBasedView.h" #import "MXKAuthInputsEmailCodeBasedView.h" +#import "MXKAuthenticationFallbackWebView.h" +#import "MXKAuthenticationRecaptchaWebView.h" #import "MXKRoomCreationInputs.h" diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m b/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m index 8e764eb3a..44eb77a76 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m @@ -19,7 +19,6 @@ #import "NSBundle+MatrixKit.h" @implementation MXKAuthInputsEmailCodeBasedView -@dynamic displayNameTextField; + (UINib *)nib { @@ -34,11 +33,43 @@ - (void)awakeFromNib _userLoginTextField.placeholder = [NSBundle mxk_localizedStringForKey:@"login_user_id_placeholder"]; _emailAndTokenTextField.placeholder = [NSBundle mxk_localizedStringForKey:@"login_email_placeholder"]; _promptEmailTokenLabel.text = [NSBundle mxk_localizedStringForKey:@"login_prompt_email_token"]; + + _displayNameTextField.placeholder = [NSBundle mxk_localizedStringForKey:@"login_display_name_placeholder"]; +} + +#pragma mark - + +- (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKAuthenticationType)authType; +{ + // Validate first the provided session + MXAuthenticationSession *validSession = [self validateAuthenticationSession:authSession]; + + if ([super setAuthSession:validSession withAuthType:authType]) + { + // Set initial layout + self.userLoginTextField.hidden = NO; + self.promptEmailTokenLabel.hidden = YES; + + if (type == MXKAuthenticationTypeLogin) + { + self.emailAndTokenTextField.returnKeyType = UIReturnKeyDone; + self.displayNameTextField.hidden = YES; + } + else + { + self.emailAndTokenTextField.returnKeyType = UIReturnKeyNext; + self.displayNameTextField.hidden = NO; + } + + return YES; + } + + return NO; } - (CGFloat)actualHeight { - if (self.authType == MXKAuthenticationTypeLogin) + if (type == MXKAuthenticationTypeLogin) { return self.displayNameTextField.frame.origin.y; } @@ -54,28 +85,11 @@ - (BOOL)areAllRequiredFieldsFilled return ret; } -- (void)setAuthType:(MXKAuthenticationType)authType -{ - // Set initial layout - self.userLoginTextField.hidden = NO; - self.promptEmailTokenLabel.hidden = YES; - - if (authType == MXKAuthenticationTypeLogin) - { - self.emailAndTokenTextField.returnKeyType = UIReturnKeyDone; - } - else - { - self.emailAndTokenTextField.returnKeyType = UIReturnKeyNext; - } - - super.authType = authType; -} - - (void)dismissKeyboard { [self.userLoginTextField resignFirstResponder]; [self.emailAndTokenTextField resignFirstResponder]; + [self.displayNameTextField resignFirstResponder]; [super dismissKeyboard]; } @@ -89,6 +103,8 @@ - (void)nextStep self.promptEmailTokenLabel.hidden = NO; self.emailAndTokenTextField.placeholder = nil; self.emailAndTokenTextField.returnKeyType = UIReturnKeyDone; + + self.displayNameTextField.hidden = YES; } #pragma mark UITextField delegate @@ -122,4 +138,46 @@ - (BOOL)textFieldShouldReturn:(UITextField*)textField return YES; } +#pragma mark - + +- (MXAuthenticationSession*)validateAuthenticationSession:(MXAuthenticationSession*)authSession +{ + // Check whether at least one of the listed flow is supported. + BOOL isSupported = NO; + + for (MXLoginFlow *loginFlow in authSession.flows) + { + // Check whether flow type is defined (this type has been deprecated since C-S API v2) + if ([loginFlow.type isEqualToString:kMXLoginFlowTypeEmailCode]) + { + isSupported = YES; + break; + } + else if (loginFlow.stages.count == 1 && [loginFlow.stages.firstObject isEqualToString:kMXLoginFlowTypeEmailCode]) + { + isSupported = YES; + break; + } + } + + if (isSupported) + { + if (authSession.flows.count == 1) + { + // Return the original session. + return authSession; + } + else + { + // Keep only the supported flow. + MXAuthenticationSession *updatedAuthSession = [[MXAuthenticationSession alloc] init]; + updatedAuthSession.session = authSession.session; + updatedAuthSession.params = authSession.params; + updatedAuthSession.flows = @[[MXLoginFlow modelFromJSON:@{@"stages":@[kMXLoginFlowTypeEmailCode]}]]; + } + } + + return nil; +} + @end \ No newline at end of file diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m b/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m index a53374028..2e911179f 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m @@ -19,7 +19,6 @@ #import "NSBundle+MatrixKit.h" @implementation MXKAuthInputsPasswordBasedView -@dynamic displayNameTextField; + (UINib *)nib { @@ -35,11 +34,43 @@ - (void)awakeFromNib _passWordTextField.placeholder = [NSBundle mxk_localizedStringForKey:@"login_password_placeholder"]; _emailTextField.placeholder = [NSString stringWithFormat:@"%@ (%@)", [NSBundle mxk_localizedStringForKey:@"login_email_placeholder"], [NSBundle mxk_localizedStringForKey:@"login_optional_field"]]; _emailInfoLabel.text = [NSBundle mxk_localizedStringForKey:@"login_email_info"]; + + _displayNameTextField.placeholder = [NSBundle mxk_localizedStringForKey:@"login_display_name_placeholder"]; +} + +#pragma mark - + +- (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKAuthenticationType)authType; +{ + // Validate first the provided session + MXAuthenticationSession *validSession = [self validateAuthenticationSession:authSession]; + + if ([super setAuthSession:validSession withAuthType:authType]) + { + if (type == MXKAuthenticationTypeLogin) + { + self.passWordTextField.returnKeyType = UIReturnKeyDone; + self.emailTextField.hidden = YES; + self.emailInfoLabel.hidden = YES; + self.displayNameTextField.hidden = YES; + } + else + { + self.passWordTextField.returnKeyType = UIReturnKeyNext; + self.emailTextField.hidden = NO; + self.emailInfoLabel.hidden = NO; + self.displayNameTextField.hidden = NO; + } + + return YES; + } + + return NO; } - (CGFloat)actualHeight { - if (self.authType == MXKAuthenticationTypeLogin) + if (type == MXKAuthenticationTypeLogin) { return self.displayNameTextField.frame.origin.y; } @@ -55,28 +86,12 @@ - (BOOL)areAllRequiredFieldsFilled return ret; } -- (void)setAuthType:(MXKAuthenticationType)authType -{ - if (authType == MXKAuthenticationTypeLogin) - { - self.passWordTextField.returnKeyType = UIReturnKeyDone; - self.emailTextField.hidden = YES; - self.emailInfoLabel.hidden = YES; - } - else - { - self.passWordTextField.returnKeyType = UIReturnKeyNext; - self.emailTextField.hidden = NO; - self.emailInfoLabel.hidden = NO; - } - super.authType = authType; -} - - (void)dismissKeyboard { [self.userLoginTextField resignFirstResponder]; [self.passWordTextField resignFirstResponder]; [self.emailTextField resignFirstResponder]; + [self.displayNameTextField resignFirstResponder]; [super dismissKeyboard]; } @@ -115,4 +130,47 @@ - (BOOL)textFieldShouldReturn:(UITextField*)textField return YES; } + +#pragma mark - + +- (MXAuthenticationSession*)validateAuthenticationSession:(MXAuthenticationSession*)authSession +{ + // Check whether at least one of the listed flow is supported. + BOOL isSupported = NO; + + for (MXLoginFlow *loginFlow in authSession.flows) + { + // Check whether flow type is defined (this type has been deprecated since C-S API v2) + if ([loginFlow.type isEqualToString:kMXLoginFlowTypePassword]) + { + isSupported = YES; + break; + } + else if (loginFlow.stages.count == 1 && [loginFlow.stages.firstObject isEqualToString:kMXLoginFlowTypePassword]) + { + isSupported = YES; + break; + } + } + + if (isSupported) + { + if (authSession.flows.count == 1) + { + // Return the original session. + return authSession; + } + else + { + // Keep only the supported flow. + MXAuthenticationSession *updatedAuthSession = [[MXAuthenticationSession alloc] init]; + updatedAuthSession.session = authSession.session; + updatedAuthSession.params = authSession.params; + updatedAuthSession.flows = @[[MXLoginFlow modelFromJSON:@{@"stages":@[kMXLoginFlowTypePassword]}]]; + } + } + + return nil; +} + @end diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsView.h b/MatrixKit/Views/Authentication/MXKAuthInputsView.h index 82b8ae74c..5ccb5048d 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsView.h +++ b/MatrixKit/Views/Authentication/MXKAuthInputsView.h @@ -14,7 +14,7 @@ limitations under the License. */ -#import +#import /** Authentication type: register or login @@ -43,42 +43,67 @@ typedef enum { `MXKAuthInputsView` is a base class to handle authentication inputs. */ @interface MXKAuthInputsView : UIView +{ +@protected + /** + The authentication type (`MXKAuthenticationTypeLogin` by default). + */ + MXKAuthenticationType type; + + /** + The authentication session (nil by default). + */ + MXAuthenticationSession *session; +} /** - The authentication type (`MXKAuthenticationTypeLogin` by default). + The view delegate. */ -@property (nonatomic) MXKAuthenticationType authType; +@property (nonatomic) id delegate; /** - The view delegate. + The current authentication type (`MXKAuthenticationTypeLogin` by default). */ -@property (nonatomic) id delegate; +@property (nonatomic, readonly) MXKAuthenticationType authType; /** - The text field related to the display name (nil by default). - This item is optional, it may be displayed in case of registration. + The current authentication session if any. */ -@property (weak, nonatomic) UITextField *displayNameTextField; +@property (nonatomic, readonly) MXAuthenticationSession *authSession; +/** + The layout constraint defined on the view height. + */ @property (weak, nonatomic) IBOutlet NSLayoutConstraint *viewHeightConstraint; /** - * Returns the `UINib` object initialized for the auth inputs view. - * - * @return The initialized `UINib` object or `nil` if there were errors during - * initialization or the nib file could not be located. + Returns the `UINib` object initialized for the auth inputs view. + + @return The initialized `UINib` object or `nil` if there were errors during + initialization or the nib file could not be located. */ + (UINib *)nib; /** - * Creates and returns a new `MXKAuthInputsView` object. - * - * @discussion This is the designated initializer for programmatic instantiation. - * - * @return An initialized `MXKAuthInputsView` object if successful, `nil` otherwise. + Creates and returns a new `MXKAuthInputsView` object. + + @discussion This is the designated initializer for programmatic instantiation. + + @return An initialized `MXKAuthInputsView` object if successful, `nil` otherwise. */ + (instancetype)authInputsView; +/** + Finalize the authentication inputs view with a session and a type. + + @discussion You may override this method to check/update the flows listed in the provided authentication session. + + @param authSession the authentication session returned by the homeserver. + @param authType the authentication type (see 'MXKAuthenticationType'). + @return YES if the provided session and type are supported by the MXKAuthInputsView-inherited class. Note the unsupported flows should be here removed from the stored authentication session (see the resulting session in the property named 'authSession'). + */ +- (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKAuthenticationType)authType; + /** The actual view height. This height takes into account shown/hidden fields. */ @@ -99,8 +124,4 @@ typedef enum { */ - (void)nextStep; -/** - Return in initial step of the authentication flow. - */ -- (void)resetStep; @end diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsView.m b/MatrixKit/Views/Authentication/MXKAuthInputsView.m index 2a628b91e..6450f2471 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsView.m @@ -41,12 +41,9 @@ - (void)awakeFromNib { [super awakeFromNib]; - // Localize string - _displayNameTextField.placeholder = [NSBundle mxk_localizedStringForKey:@"login_display_name_placeholder"]; - [self setTranslatesAutoresizingMaskIntoConstraints: NO]; - self.authType = MXKAuthenticationTypeLogin; + type = MXKAuthenticationTypeLogin; } - (instancetype)init @@ -54,13 +51,26 @@ - (instancetype)init self = [super init]; if (self) { - self.authType = MXKAuthenticationTypeLogin; + type = MXKAuthenticationTypeLogin; } return self; } #pragma mark - +- (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKAuthenticationType)authType; +{ + if (authSession) + { + type = authType; + session = authSession; + + return YES; + } + + return NO; +} + - (CGFloat)actualHeight { return _viewHeightConstraint.constant; @@ -72,32 +82,26 @@ - (BOOL)areAllRequiredFieldsFilled return YES; } -- (void)setAuthType:(MXKAuthenticationType)authType +- (void)dismissKeyboard { - if (authType == MXKAuthenticationTypeLogin) - { - self.displayNameTextField.hidden = YES; - } - else - { - self.displayNameTextField.hidden = NO; - } - _authType = authType; + } -- (void)dismissKeyboard +- (void)nextStep { - [self.displayNameTextField resignFirstResponder]; + } -- (void)nextStep +#pragma mark - + +- (MXKAuthenticationType)authType { - self.displayNameTextField.hidden = YES; + return type; } -- (void)resetStep +- (MXAuthenticationSession*)authSession { - self.authType = _authType; + return session; } @end \ No newline at end of file diff --git a/MatrixKit/Views/Authentication/MXKAuthenticationRecaptchaWebView.h b/MatrixKit/Views/Authentication/MXKAuthenticationRecaptchaWebView.h new file mode 100644 index 000000000..9b7f9ef5a --- /dev/null +++ b/MatrixKit/Views/Authentication/MXKAuthenticationRecaptchaWebView.h @@ -0,0 +1,30 @@ +/* + Copyright 2016 OpenMarket Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +@interface MXKAuthenticationRecaptchaWebView : UIWebView + +/** + Open reCAPTCHA widget into a webview. + + @param siteKey the site key. + @param homeServer the home server URL. + @param callback the block called when the user has received reCAPTCHA response. + */ +- (void)openRecaptchaWidgetWithSiteKey:(NSString*)siteKey fromHomeServer:(NSString*)homeServer callback:(void (^)(NSString *response))callback; + +@end diff --git a/MatrixKit/Views/Authentication/MXKAuthenticationRecaptchaWebView.m b/MatrixKit/Views/Authentication/MXKAuthenticationRecaptchaWebView.m new file mode 100644 index 000000000..454bb22b0 --- /dev/null +++ b/MatrixKit/Views/Authentication/MXKAuthenticationRecaptchaWebView.m @@ -0,0 +1,129 @@ +/* + Copyright 2016 OpenMarket Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXKAuthenticationRecaptchaWebView.h" + +NSString *kMXKRecaptchaHTMLString = @" \ + \ + \ + \ + \ +
\ +
\ +
\ + \ + \ +"; + +@interface MXKAuthenticationRecaptchaWebView () +{ + // The block called when the reCAPTCHA response is received + void (^onResponse)(NSString *); + + // Activity indicator + UIActivityIndicatorView *activityIndicator; +} +@end + +@implementation MXKAuthenticationRecaptchaWebView + +- (void)dealloc +{ + if (activityIndicator) + { + [activityIndicator removeFromSuperview]; + activityIndicator = nil; + } +} + +- (void)openRecaptchaWidgetWithSiteKey:(NSString*)siteKey fromHomeServer:(NSString*)homeServer callback:(void (^)(NSString *response))callback +{ + self.delegate = self; + + onResponse = callback; + + // Add activity indicator + activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + activityIndicator.center = self.center; + [self addSubview:activityIndicator]; + [activityIndicator startAnimating]; + +// // Delete cookies to launch login process from scratch +// for(NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) +// { +// [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie]; +// } + + + [self loadHTMLString:kMXKRecaptchaHTMLString baseURL:[NSURL URLWithString:homeServer]]; +} + +-(void)webViewDidFinishLoad:(UIWebView *)webView +{ + if (activityIndicator) + { + [activityIndicator stopAnimating]; + [activityIndicator removeFromSuperview]; + activityIndicator = nil; + } +} + +- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType +{ + NSString *urlString = [[request URL] absoluteString]; + + if ([urlString hasPrefix:@"js:"]) + { + // Listen only to scheme of the JS-UIWebView bridge + NSString *jsonString = [[[urlString componentsSeparatedByString:@"js:"] lastObject] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding]; + NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; + + NSError *error; + NSDictionary *parameters = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers + error:&error]; + + if (!error) + { + if ([@"verifyCallback" isEqualToString:parameters[@"action"]]) + { + // Transfer the reCAPTCHA response + onResponse(parameters[@"response"]); + } + } + return NO; + } + return YES; +} + +@end From bfeac7f3443759121bceeba0c1d893b01d7627bb Mon Sep 17 00:00:00 2001 From: giomfo Date: Fri, 1 Apr 2016 16:20:37 +0200 Subject: [PATCH 11/30] Registration support --- .../MXKAuthenticationViewController.h | 19 ++ .../MXKAuthenticationViewController.m | 269 +++++++++++++----- MatrixKit/Models/MXK3PID.h | 10 + .../MXKAuthInputsEmailCodeBasedView.m | 20 +- .../MXKAuthInputsPasswordBasedView.m | 49 +++- .../Views/Authentication/MXKAuthInputsView.h | 58 +++- .../Views/Authentication/MXKAuthInputsView.m | 37 ++- .../MXKAuthenticationRecaptchaWebView.m | 22 +- 8 files changed, 368 insertions(+), 116 deletions(-) diff --git a/MatrixKit/Controllers/MXKAuthenticationViewController.h b/MatrixKit/Controllers/MXKAuthenticationViewController.h index 00b629e34..ef62d50a2 100644 --- a/MatrixKit/Controllers/MXKAuthenticationViewController.h +++ b/MatrixKit/Controllers/MXKAuthenticationViewController.h @@ -48,6 +48,14 @@ extern NSString *const MXKAuthErrorDomain; You may add a delegate to be notified when a new account has been added successfully. */ @interface MXKAuthenticationViewController : MXKViewController +{ +@protected + + /** + Reference to any opened alert view. + */ + MXKAlert *alert; +} @property (weak, nonatomic) IBOutlet UIImageView *welcomeImageView; @@ -101,6 +109,12 @@ extern NSString *const MXKAuthErrorDomain; */ @property (nonatomic) NSString *defaultIdentityServerUrl; +/** + Enable/disable overall the user interaction option. + It is used during authentication process to prevent multiple requests. + */ +@property(nonatomic,getter=isUserInteractionEnabled) BOOL userInteractionEnabled; + /** The delegate for the view controller. */ @@ -163,5 +177,10 @@ extern NSString *const MXKAuthErrorDomain; */ - (IBAction)onButtonPressed:(id)sender; +/** + Force dismiss keyboard + */ +- (void)dismissKeyboard; + @end diff --git a/MatrixKit/Controllers/MXKAuthenticationViewController.m b/MatrixKit/Controllers/MXKAuthenticationViewController.m index 10150a307..6fe50b850 100644 --- a/MatrixKit/Controllers/MXKAuthenticationViewController.m +++ b/MatrixKit/Controllers/MXKAuthenticationViewController.m @@ -40,11 +40,6 @@ @interface MXKAuthenticationViewController () */ MXHTTPOperation *mxCurrentOperation; - /** - Reference to any opened alert view. - */ - MXKAlert *alert; - /** The MXKAuthInputsView class or a sub-class used when logging in. */ @@ -299,7 +294,20 @@ - (void)setAuthType:(MXKAuthenticationType)authType [_authSwitchButton setTitle:[NSBundle mxk_localizedStringForKey:@"back"] forState:UIControlStateHighlighted]; } - _authType = authType; + + if (_authType != authType) + { + _authType = authType; + + // Remove the current inputs view + self.authInputsView = nil; + + [self.authInputsContainerView bringSubviewToFront: _authenticationActivityIndicator]; + [_authenticationActivityIndicator startAnimating]; + } + + // Restore user interaction + self.userInteractionEnabled = YES; // Update supported authentication flow and associated information (defined in authentication session) [self refreshAuthenticationSession]; @@ -314,7 +322,7 @@ - (void)setAuthInputsView:(MXKAuthInputsView *)authInputsView if (_authInputsView) { - [_authInputsView removeObserver:self forKeyPath:@"actualHeight"]; + [_authInputsView removeObserver:self forKeyPath:@"viewHeightConstraint.constant"]; if ([NSLayoutConstraint respondsToSelector:@selector(deactivateConstraints:)]) { @@ -327,6 +335,7 @@ - (void)setAuthInputsView:(MXKAuthInputsView *)authInputsView [_authInputsView removeFromSuperview]; _authInputsView.delegate = nil; + [_authInputsView destroy]; _authInputsView = nil; } @@ -336,7 +345,7 @@ - (void)setAuthInputsView:(MXKAuthInputsView *)authInputsView if (_authInputsView) { - _authInputsView.translatesAutoresizingMaskIntoConstraints = NO; // FIXME GFO useful or not? + _authInputsView.translatesAutoresizingMaskIntoConstraints = NO; [_authInputsContainerView addSubview:_authInputsView]; _authInputsView.delegate = self; @@ -344,7 +353,7 @@ - (void)setAuthInputsView:(MXKAuthInputsView *)authInputsView _submitButton.hidden = NO; _authInputsView.hidden = NO; - _authInputContainerViewHeightConstraint.constant = _authInputsView.actualHeight; + _authInputContainerViewHeightConstraint.constant = _authInputsView.viewHeightConstraint.constant; NSLayoutConstraint* topConstraint = [NSLayoutConstraint constraintWithItem:_authInputsContainerView attribute:NSLayoutAttributeTop @@ -383,7 +392,7 @@ - (void)setAuthInputsView:(MXKAuthInputsView *)authInputsView [_authInputsContainerView addConstraint:trailingConstraint]; } - [_authInputsView addObserver:self forKeyPath:@"actualHeight" options:0 context:nil]; + [_authInputsView addObserver:self forKeyPath:@"viewHeightConstraint.constant" options:0 context:nil]; } else { @@ -423,6 +432,17 @@ - (void)setDefaultIdentityServerUrl:(NSString *)defaultIdentityServerUrl } } +- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled +{ + _submitButton.enabled = (userInteractionEnabled && _authInputsView.areAllRequiredFieldsFilled); + _authSwitchButton.enabled = userInteractionEnabled; + + _homeServerTextField.enabled = userInteractionEnabled; + _identityServerTextField.enabled = userInteractionEnabled; + + _userInteractionEnabled = userInteractionEnabled; +} + - (void)refreshAuthenticationSession { // Remove reachability observer @@ -432,8 +452,6 @@ - (void)refreshAuthenticationSession [mxCurrentOperation cancel]; mxCurrentOperation = nil; - [_authenticationActivityIndicator stopAnimating]; - // Reset potential authentication fallback url authenticationFallback = nil; @@ -470,9 +488,10 @@ - (void)refreshAuthenticationSession - (void)handleAuthenticationSession:(MXAuthenticationSession *)authSession { - [_authenticationActivityIndicator stopAnimating]; mxCurrentOperation = nil; + [_authenticationActivityIndicator stopAnimating]; + // Check whether fallback is defined, and instantiate an auth inputs view (if a class is defined). MXKAuthInputsView *authInputsView; if (_authType == MXKAuthenticationTypeLogin) @@ -497,7 +516,7 @@ - (void)handleAuthenticationSession:(MXAuthenticationSession *)authSession if (authInputsView) { // Apply authentication session on inputs view - if ([authInputsView setAuthSession:authSession withAuthType:_authType]) + if ([authInputsView setAuthSession:authSession withAuthType:_authType] == NO) { NSLog(@"[MXKAuthenticationVC] Received authentication settings are not supported"); authInputsView = nil; @@ -559,44 +578,59 @@ - (IBAction)onButtonPressed:(id)sender if (sender == _submitButton) { - if (mxRestClient) - { - // Disable user interaction to prevent multiple requests - [self setUserInteractionEnabled:NO]; - [self.authInputsContainerView bringSubviewToFront: _authenticationActivityIndicator]; - [_authenticationActivityIndicator startAnimating]; + // Disable user interaction to prevent multiple requests + self.userInteractionEnabled = NO; + [self.authInputsContainerView bringSubviewToFront: _authenticationActivityIndicator]; + + // Launch authentication by preparing parameters dict + [self.authInputsView prepareParameters:^(NSDictionary *parameters) { - if (_authType == MXKAuthenticationTypeLogin) + if (parameters && mxRestClient) { - // if ([selectedStages indexOfObject:kMXLoginFlowTypePassword] != NSNotFound) FIXME GFO + [_authenticationActivityIndicator startAnimating]; + + if (_authType == MXKAuthenticationTypeLogin) { - MXKAuthInputsPasswordBasedView *authInputsView = (MXKAuthInputsPasswordBasedView*)_authInputsView; - - NSString *user = authInputsView.userLoginTextField.text; - user = [user stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - - [mxRestClient loginWithUser:user andPassword:authInputsView.passWordTextField.text - success:^(MXCredentials *credentials){ - [_authenticationActivityIndicator stopAnimating]; - - [self onSuccessfulLogin:credentials]; - } - failure:^(NSError *error){ - [self onFailureDuringAuthRequest:error]; - }]; + mxCurrentOperation = [mxRestClient login:parameters success:^(NSDictionary *JSONResponse) { + + MXCredentials *credentials = [MXCredentials modelFromJSON:JSONResponse]; + + // Sanity check + if (!credentials.userId || !credentials.accessToken) + { + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; + } + else + { + NSLog(@"[MXKAuthenticationVC] Login process succeeded"); + + // Workaround: HS does not return the right URL. Use the one we used to make the request + credentials.homeServer = mxRestClient.homeserver; + // Report the certificate trusted by user (if any) + credentials.allowedCertificate = mxRestClient.allowedCertificate; + + [self onSuccessfulLogin:credentials]; + } + + } failure:^(NSError *error) { + + [self onFailureDuringAuthRequest:error]; + + }]; + } + else + { + [self registerWithParameters:parameters]; } - // else - // { - // // FIXME GFO - // [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; - // } } else { - // FIXME + NSLog(@"[MXKAuthenticationVC] Failed to prepare parameters"); + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; } - } + + }]; } else if (sender == _authSwitchButton) { @@ -703,27 +737,109 @@ - (void)updateRESTClient } } -- (void)setUserInteractionEnabled:(BOOL)isEnabled +- (void)registerWithParameters:(NSDictionary*)parameters { - _submitButton.enabled = (isEnabled && _authInputsView.areAllRequiredFieldsFilled); - _authSwitchButton.enabled = isEnabled; - - _homeServerTextField.enabled = isEnabled; - _identityServerTextField.enabled = isEnabled; + mxCurrentOperation = [mxRestClient registerWithParameters:parameters success:^(NSDictionary *JSONResponse) { + + MXCredentials *credentials = [MXCredentials modelFromJSON:JSONResponse]; + + // Sanity check + if (!credentials.userId || !credentials.accessToken) + { + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; + } + else + { + NSLog(@"[MXKAuthenticationVC] Registration succeeded"); + // Workaround: HS does not return the right URL. Use the one we used to make the request + credentials.homeServer = mxRestClient.homeserver; + // Report the certificate trusted by user (if any) + credentials.allowedCertificate = mxRestClient.allowedCertificate; + + [self onSuccessfulLogin:credentials]; + } + + } failure:^(NSError *error) { + + mxCurrentOperation = nil; + + // Check whether the authentication is pending (for example waiting for email validation) + MXError *mxError = [[MXError alloc] initWithNSError:error]; + if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringUnauthorized]) + { + NSLog(@"[MXKAuthenticationVC] Wait for email validation"); + + // Loop + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + + NSLog(@"[MXKAuthenticationVC] Retry registration"); + [self registerWithParameters:parameters]; + + }); + } + else + { + // C-S API v2: The completed stages should be available in response data in case of unauthorized request. + NSDictionary *JSONResponse = nil; + if (error.userInfo[MXHTTPClientErrorResponseDataKey]) + { + JSONResponse = error.userInfo[MXHTTPClientErrorResponseDataKey]; + } + + if (JSONResponse) + { + MXAuthenticationSession *authSession = [MXAuthenticationSession modelFromJSON:JSONResponse]; + + if ([authSession.session isEqualToString:self.authInputsView.authSession.session]) + { + if (authSession.completed) + { + [_authenticationActivityIndicator stopAnimating]; + + [self.authInputsView updateAuthSessionWithCompletedStages:authSession.completed didUpdateParameters:^(NSDictionary *parameters) { + + if (parameters) + { + NSLog(@"[MXKAuthenticationVC] Pursue registration"); + + [_authenticationActivityIndicator startAnimating]; + [self registerWithParameters:parameters]; + } + else + { + NSLog(@"[MXKAuthenticationVC] Failed to update parameters"); + + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; + } + + }]; + + return; + } + } + + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; + } + else + { + [self onFailureDuringAuthRequest:error]; + } + } + }]; } - (void)onFailureDuringMXOperation:(NSError*)error { mxCurrentOperation = nil; + [_authenticationActivityIndicator stopAnimating]; + if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { // Ignore this error return; } - [_authenticationActivityIndicator stopAnimating]; - // Alert user NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; if (!title) @@ -799,8 +915,9 @@ - (void)onReachabilityStatusChange:(NSNotification *)notif - (void)onFailureDuringAuthRequest:(NSError *)error { + mxCurrentOperation = nil; [_authenticationActivityIndicator stopAnimating]; - [self setUserInteractionEnabled:YES]; + self.userInteractionEnabled = YES; NSLog(@"[MXKAuthenticationVC] Auth request failed: %@", error); @@ -816,31 +933,31 @@ - (void)onFailureDuringAuthRequest:(NSError *)error if (errCode) { - if ([errCode isEqualToString:@"M_FORBIDDEN"]) + if ([errCode isEqualToString:kMXErrCodeStringForbidden]) { message = [NSBundle mxk_localizedStringForKey:@"login_error_forbidden"]; } - else if ([errCode isEqualToString:@"M_UNKNOWN_TOKEN"]) + else if ([errCode isEqualToString:kMXErrCodeStringUnknownToken]) { message = [NSBundle mxk_localizedStringForKey:@"login_error_unknown_token"]; } - else if ([errCode isEqualToString:@"M_BAD_JSON"]) + else if ([errCode isEqualToString:kMXErrCodeStringBadJSON]) { message = [NSBundle mxk_localizedStringForKey:@"login_error_bad_json"]; } - else if ([errCode isEqualToString:@"M_NOT_JSON"]) + else if ([errCode isEqualToString:kMXErrCodeStringNotJSON]) { message = [NSBundle mxk_localizedStringForKey:@"login_error_not_json"]; } - else if ([errCode isEqualToString:@"M_LIMIT_EXCEEDED"]) + else if ([errCode isEqualToString:kMXErrCodeStringLimitExceeded]) { message = [NSBundle mxk_localizedStringForKey:@"login_error_limit_exceeded"]; } - else if ([errCode isEqualToString:@"M_USER_IN_USE"]) + else if ([errCode isEqualToString:kMXErrCodeStringUserInUse]) { message = [NSBundle mxk_localizedStringForKey:@"login_error_user_in_use"]; } - else if ([errCode isEqualToString:@"M_LOGIN_EMAIL_URL_NOT_YET"]) + else if ([errCode isEqualToString:kMXErrCodeStringLoginEmailURLNotYet]) { message = [NSBundle mxk_localizedStringForKey:@"login_error_login_email_not_yet"]; } @@ -860,10 +977,17 @@ - (void)onFailureDuringAuthRequest:(NSError *)error [alert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"dismiss"] style:MXKAlertActionStyleCancel handler:^(MXKAlert *alert) {}]; [alert showInViewController:self]; + + // Update authentication inputs view to return in initial step + [self.authInputsView setAuthSession:self.authInputsView.authSession withAuthType:_authType]; } - (void)onSuccessfulLogin:(MXCredentials*)credentials { + mxCurrentOperation = nil; + [_authenticationActivityIndicator stopAnimating]; + self.userInteractionEnabled = YES; + // Sanity check: check whether the user is not already logged in with this id if ([[MXKAccountManager sharedManager] accountForUserId:credentials.userId]) { @@ -921,8 +1045,6 @@ - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField [mxCurrentOperation cancel]; mxCurrentOperation = nil; } - - [_authenticationActivityIndicator stopAnimating]; } return YES; @@ -971,7 +1093,13 @@ - (BOOL)textFieldShouldReturn:(UITextField*)textField #pragma mark - AuthInputsViewDelegate delegate -- (void)authInputsDoneKeyHasBeenPressed:(MXKAuthInputsView *)authInputsView +- (void)authInputsView:(MXKAuthInputsView*)authInputsView presentMXKAlert:(MXKAlert*)inputsAlert +{ + [self dismissKeyboard]; + [inputsAlert showInViewController:self]; +} + +- (void)authInputsViewDidPressDoneKey:(MXKAuthInputsView *)authInputsView { if (_submitButton.isEnabled) { @@ -980,6 +1108,11 @@ - (void)authInputsDoneKeyHasBeenPressed:(MXKAuthInputsView *)authInputsView } } +- (MXRestClient *)authInputsViewEmailValidationRestClient:(MXKAuthInputsView *)authInputsView +{ + return mxRestClient; +} + #pragma mark - Authentication Fallback - (void)showAuthenticationFallBackView:(NSString*)fallbackPage @@ -1029,11 +1162,17 @@ - (void)hideRegistrationFallbackView - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if ([@"actualHeight" isEqualToString:keyPath]) + if ([@"viewHeightConstraint.constant" isEqualToString:keyPath]) { // Refresh the height of the auth inputs view container. - _authInputContainerViewHeightConstraint.constant = _authInputsView.actualHeight; - [self.view setNeedsUpdateConstraints]; + CGFloat previousInputsContainerViewHeight = _authInputContainerViewHeightConstraint.constant; + _authInputContainerViewHeightConstraint.constant = _authInputsView.viewHeightConstraint.constant; + + // Force to render the view + [self.view layoutIfNeeded]; + + // Refresh content view height by considering the updated height of inputs container + _contentViewHeightConstraint.constant += (_authInputContainerViewHeightConstraint.constant - previousInputsContainerViewHeight); } else { diff --git a/MatrixKit/Models/MXK3PID.h b/MatrixKit/Models/MXK3PID.h index f3c7d337a..222d003f2 100644 --- a/MatrixKit/Models/MXK3PID.h +++ b/MatrixKit/Models/MXK3PID.h @@ -37,6 +37,16 @@ typedef enum : NSUInteger { */ @property (nonatomic, readonly) NSString *address; +/** + The current client secret key used during email validation. + */ +@property (nonatomic, readonly) NSString *clientSecret; + +/** + The current session identifier during email validation. + */ +@property (nonatomic, readonly) NSString *sid; + /** The id of the user on Matrix. nil if unknown or not yet resolved. diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m b/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m index 44eb77a76..2061ec4d4 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m @@ -54,11 +54,15 @@ - (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKA { self.emailAndTokenTextField.returnKeyType = UIReturnKeyDone; self.displayNameTextField.hidden = YES; + + self.viewHeightConstraint.constant = self.displayNameTextField.frame.origin.y; } else { self.emailAndTokenTextField.returnKeyType = UIReturnKeyNext; self.displayNameTextField.hidden = NO; + + self.viewHeightConstraint.constant = 122; } return YES; @@ -67,15 +71,6 @@ - (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKA return NO; } -- (CGFloat)actualHeight -{ - if (type == MXKAuthenticationTypeLogin) - { - return self.displayNameTextField.frame.origin.y; - } - return super.actualHeight; -} - - (BOOL)areAllRequiredFieldsFilled { BOOL ret = [super areAllRequiredFieldsFilled]; @@ -116,11 +111,8 @@ - (BOOL)textFieldShouldReturn:(UITextField*)textField // "Done" key has been pressed [textField resignFirstResponder]; - if (self.delegate && [self.delegate respondsToSelector:@selector(authInputsDoneKeyHasBeenPressed:)]) - { - // Launch authentication now - [self.delegate authInputsDoneKeyHasBeenPressed:self]; - } + // Launch authentication now + [self.delegate authInputsViewDidPressDoneKey:self]; } else { diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m b/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m index 2e911179f..15012554b 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m @@ -53,6 +53,8 @@ - (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKA self.emailTextField.hidden = YES; self.emailInfoLabel.hidden = YES; self.displayNameTextField.hidden = YES; + + self.viewHeightConstraint.constant = self.displayNameTextField.frame.origin.y; } else { @@ -60,6 +62,8 @@ - (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKA self.emailTextField.hidden = NO; self.emailInfoLabel.hidden = NO; self.displayNameTextField.hidden = NO; + + self.viewHeightConstraint.constant = 179; } return YES; @@ -68,13 +72,43 @@ - (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKA return NO; } -- (CGFloat)actualHeight +- (void)prepareParameters:(void (^)(NSDictionary *parameters))callback; { - if (type == MXKAuthenticationTypeLogin) + if (callback) { - return self.displayNameTextField.frame.origin.y; + // Sanity check on required fields + if (self.areAllRequiredFieldsFilled == NO) + { + callback(nil); + } + + // Retrieve the user login and check whether it is an email or a username. + NSString *user = self.userLoginTextField.text; + user = [user stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + BOOL isEmailAddress = [MXTools isEmailAddress:user]; + + NSDictionary *parameters; + + if (isEmailAddress) + { + parameters = @{ + @"type": kMXLoginFlowTypePassword, + @"medium": @"email", + @"address": user, + @"password": self.passWordTextField.text + }; + } + else + { + parameters = @{ + @"type": kMXLoginFlowTypePassword, + @"user": user, + @"password": self.passWordTextField.text + }; + } + + callback(parameters); } - return super.actualHeight; } - (BOOL)areAllRequiredFieldsFilled @@ -105,11 +139,8 @@ - (BOOL)textFieldShouldReturn:(UITextField*)textField // "Done" key has been pressed [textField resignFirstResponder]; - if (self.delegate && [self.delegate respondsToSelector:@selector(authInputsDoneKeyHasBeenPressed:)]) - { - // Launch authentication now - [self.delegate authInputsDoneKeyHasBeenPressed:self]; - } + // Launch authentication now + [self.delegate authInputsViewDidPressDoneKey:self]; } else { diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsView.h b/MatrixKit/Views/Authentication/MXKAuthInputsView.h index 5ccb5048d..28d8cd773 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsView.h +++ b/MatrixKit/Views/Authentication/MXKAuthInputsView.h @@ -16,6 +16,8 @@ #import +#import "MXKAlert.h" + /** Authentication type: register or login */ @@ -30,13 +32,26 @@ typedef enum { `MXKAuthInputsView` delegate */ @protocol MXKAuthInputsViewDelegate -@optional +/** + Tells the delegate that a MXKAlert must be presented. + + @param authInputsView the authentication inputs view. + @param alert the alert to present. + */ +- (void)authInputsView:(MXKAuthInputsView*)authInputsView presentMXKAlert:(MXKAlert*)alert; + /** For some input fields, the return key of the keyboard is defined as `Done` key. - By this method, the delegate is notified when this key is pressed. The set of inputs may be considered to - process the current authentication step. + By this method, the delegate is notified when this key is pressed. */ -- (void)authInputsDoneKeyHasBeenPressed:(MXKAuthInputsView *)mxkAuthInputsView; +- (void)authInputsViewDidPressDoneKey:(MXKAuthInputsView *)authInputsView; + +@optional + +/** + The matrix REST Client used to validate potential email address. + */ +- (MXRestClient *)authInputsViewEmailValidationRestClient:(MXKAuthInputsView *)authInputsView; @end /** @@ -53,7 +68,12 @@ typedef enum { /** The authentication session (nil by default). */ - MXAuthenticationSession *session; + MXAuthenticationSession *currentSession; + + /** + Alert used to display inputs error. + */ + MXKAlert *inputsAlert; } /** @@ -72,7 +92,7 @@ typedef enum { @property (nonatomic, readonly) MXAuthenticationSession *authSession; /** - The layout constraint defined on the view height. + The layout constraint defined on the view height. This height takes into account shown/hidden fields. */ @property (weak, nonatomic) IBOutlet NSLayoutConstraint *viewHeightConstraint; @@ -105,15 +125,32 @@ typedef enum { - (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKAuthenticationType)authType; /** - The actual view height. This height takes into account shown/hidden fields. + Prepare the set of the inputs in order to launch an authentication process. + + @param callback the block called when the parameters are prepared. The resulting parameter dictionary is nil + if something fails (for example when a parameter or a required input is missing). + */ +- (void)prepareParameters:(void (^)(NSDictionary *parameters))callback; + +/** + Update the current authentication session by providing the list of successful stages. + + @param completedStages the list of stages the client has completed successfully. This is an array of MXLoginFlowType. + @param callback the block called when the parameters have been updated for the next stage. The resulting parameter dictionary is nil + if something fails (for example when a parameter or a required input is missing). */ -- (CGFloat)actualHeight; +- (void)updateAuthSessionWithCompletedStages:(NSArray *)completedStages didUpdateParameters:(void (^)(NSDictionary *parameters))callback; /** YES when all required fields are filled. */ - (BOOL)areAllRequiredFieldsFilled; +/** + Tell whether the email field is empty while the email binding is supported. + */ +- (BOOL)shouldPromptUserForEmailAddress; + /** Force dismiss keyboard */ @@ -124,4 +161,9 @@ typedef enum { */ - (void)nextStep; +/** + Dispose any resources and listener. + */ +- (void)destroy; + @end diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsView.m b/MatrixKit/Views/Authentication/MXKAuthInputsView.m index 6450f2471..2216c24c4 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsView.m @@ -58,12 +58,12 @@ - (instancetype)init #pragma mark - -- (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKAuthenticationType)authType; +- (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKAuthenticationType)authType { if (authSession) { type = authType; - session = authSession; + currentSession = authSession; return YES; } @@ -71,9 +71,22 @@ - (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKA return NO; } -- (CGFloat)actualHeight +- (void)prepareParameters:(void (^)(NSDictionary *parameters))callback { - return _viewHeightConstraint.constant; + // Do nothing by default + if (callback) + { + callback (nil); + } +} + +- (void)updateAuthSessionWithCompletedStages:(NSArray *)completedStages didUpdateParameters:(void (^)(NSDictionary *parameters))callback +{ + // Do nothing by default + if (callback) + { + callback (nil); + } } - (BOOL)areAllRequiredFieldsFilled @@ -82,6 +95,11 @@ - (BOOL)areAllRequiredFieldsFilled return YES; } +- (BOOL)shouldPromptUserForEmailAddress +{ + return NO; +} + - (void)dismissKeyboard { @@ -92,6 +110,15 @@ - (void)nextStep } +- (void)destroy +{ + if (inputsAlert) + { + [inputsAlert dismiss:NO]; + inputsAlert = nil; + } +} + #pragma mark - - (MXKAuthenticationType)authType @@ -101,7 +128,7 @@ - (MXKAuthenticationType)authType - (MXAuthenticationSession*)authSession { - return session; + return currentSession; } @end \ No newline at end of file diff --git a/MatrixKit/Views/Authentication/MXKAuthenticationRecaptchaWebView.m b/MatrixKit/Views/Authentication/MXKAuthenticationRecaptchaWebView.m index 454bb22b0..e1b619ce7 100644 --- a/MatrixKit/Views/Authentication/MXKAuthenticationRecaptchaWebView.m +++ b/MatrixKit/Views/Authentication/MXKAuthenticationRecaptchaWebView.m @@ -20,7 +20,7 @@ \ \ \ \ -
\ -
\ -
\ - \ +
\ + \ \ "; @@ -78,15 +75,10 @@ - (void)openRecaptchaWidgetWithSiteKey:(NSString*)siteKey fromHomeServer:(NSStri activityIndicator.center = self.center; [self addSubview:activityIndicator]; [activityIndicator startAnimating]; - -// // Delete cookies to launch login process from scratch -// for(NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) -// { -// [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie]; -// } + NSString *htmlString = [NSString stringWithFormat:kMXKRecaptchaHTMLString, siteKey]; - [self loadHTMLString:kMXKRecaptchaHTMLString baseURL:[NSURL URLWithString:homeServer]]; + [self loadHTMLString:htmlString baseURL:[NSURL URLWithString:homeServer]]; } -(void)webViewDidFinishLoad:(UIWebView *)webView From 8cdd5541185d6f8758a4632e207ee633d1ffdb01 Mon Sep 17 00:00:00 2001 From: giomfo Date: Fri, 1 Apr 2016 17:02:43 +0200 Subject: [PATCH 12/30] Registration support: Remove useless constraint in auth inputs view --- .../Views/Authentication/MXKAuthInputsEmailCodeBasedView.xib | 5 ++--- .../Views/Authentication/MXKAuthInputsPasswordBasedView.xib | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.xib b/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.xib index ff3751ba0..a35e5a814 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.xib +++ b/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.xib @@ -1,7 +1,7 @@ - + - + @@ -57,7 +57,6 @@ - diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.xib b/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.xib index d838138e6..bb44d28ac 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.xib +++ b/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.xib @@ -1,7 +1,7 @@ - + - + @@ -74,7 +74,6 @@ - From 5d0925589d7a2472e1568593483b10cec032e80c Mon Sep 17 00:00:00 2001 From: giomfo Date: Fri, 1 Apr 2016 18:41:03 +0200 Subject: [PATCH 13/30] Update comments --- MatrixKit/Controllers/MXKAuthenticationViewController.m | 5 ++--- .../Views/Authentication/MXKAuthInputsEmailCodeBasedView.m | 2 +- .../Views/Authentication/MXKAuthInputsPasswordBasedView.m | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/MatrixKit/Controllers/MXKAuthenticationViewController.m b/MatrixKit/Controllers/MXKAuthenticationViewController.m index 6fe50b850..e6e0961cb 100644 --- a/MatrixKit/Controllers/MXKAuthenticationViewController.m +++ b/MatrixKit/Controllers/MXKAuthenticationViewController.m @@ -779,7 +779,7 @@ - (void)registerWithParameters:(NSDictionary*)parameters } else { - // C-S API v2: The completed stages should be available in response data in case of unauthorized request. + // The completed stages should be available in response data in case of unauthorized request. NSDictionary *JSONResponse = nil; if (error.userInfo[MXHTTPClientErrorResponseDataKey]) { @@ -849,8 +849,7 @@ - (void)onFailureDuringMXOperation:(NSError*)error NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; alert = [[MXKAlert alloc] initWithTitle:title message:msg style:MXKAlertStyleAlert]; - alert.cancelButtonIndex = [alert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"dismiss"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) - {}]; + alert.cancelButtonIndex = [alert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"dismiss"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) {}]; [alert showInViewController:self]; // Handle specific error code here diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m b/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m index 2061ec4d4..739685077 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m @@ -139,7 +139,7 @@ - (MXAuthenticationSession*)validateAuthenticationSession:(MXAuthenticationSessi for (MXLoginFlow *loginFlow in authSession.flows) { - // Check whether flow type is defined (this type has been deprecated since C-S API v2) + // Check whether flow type is defined if ([loginFlow.type isEqualToString:kMXLoginFlowTypeEmailCode]) { isSupported = YES; diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m b/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m index 15012554b..b0a292287 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m @@ -171,7 +171,7 @@ - (MXAuthenticationSession*)validateAuthenticationSession:(MXAuthenticationSessi for (MXLoginFlow *loginFlow in authSession.flows) { - // Check whether flow type is defined (this type has been deprecated since C-S API v2) + // Check whether flow type is defined if ([loginFlow.type isEqualToString:kMXLoginFlowTypePassword]) { isSupported = YES; From 09e3e82a065c723319cc075d83b67939eca13d2a Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 4 Apr 2016 10:32:11 +0200 Subject: [PATCH 14/30] New email binding: Update MXK3PID to support new email binding mechanism --- .../en.lproj/MatrixKit.strings | 4 + .../MXKAccountDetailsViewController.h | 4 +- .../MXKAccountDetailsViewController.m | 205 ++++++------------ MatrixKit/Models/MXK3PID.h | 27 +-- MatrixKit/Models/MXK3PID.m | 131 +++-------- 5 files changed, 109 insertions(+), 262 deletions(-) diff --git a/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings b/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings index bcdfdc9fb..2d04c1a03 100644 --- a/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings +++ b/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings @@ -52,6 +52,7 @@ "abort" = "Abort"; "back" = "Back"; "close" = "Close"; +"continue" = "Continue"; "discard" = "Discard"; "dismiss" = "Dismiss"; "retry" = "Retry"; @@ -116,6 +117,9 @@ "account_save_changes" = "Save changes"; "account_link_email" = "Link Email"; "account_linked_emails" = "Linked emails"; +"account_email_validation_title" = "Verification Pending"; +"account_email_validation_message" = "Please check your email and click on the link it contains. Once this is done, click continue."; +"account_email_validation_error" = "Unable to verify email address. Please check your email and click on the link it contains. Once this is done, click continue"; "account_error_display_name_change_failed" = "Display name change failed"; "account_error_picture_change_failed" = "Picture change failed"; diff --git a/MatrixKit/Controllers/MXKAccountDetailsViewController.h b/MatrixKit/Controllers/MXKAccountDetailsViewController.h index 837411da9..226180589 100644 --- a/MatrixKit/Controllers/MXKAccountDetailsViewController.h +++ b/MatrixKit/Controllers/MXKAccountDetailsViewController.h @@ -52,9 +52,7 @@ typedef void (^blockMXKAccountDetailsViewController_onReadyToLeave)(); MXK3PID *submittedEmail; UIButton *emailSubmitButton; UITextField *emailTextField; - UIButton *emailTokenSubmitButton; - UITextField *emailTokenTextField; - + // Notifications UISwitch *apnsNotificationsSwitch; UISwitch *inAppNotificationsSwitch; diff --git a/MatrixKit/Controllers/MXKAccountDetailsViewController.m b/MatrixKit/Controllers/MXKAccountDetailsViewController.m index 47ec8b329..dbe2df161 100644 --- a/MatrixKit/Controllers/MXKAccountDetailsViewController.m +++ b/MatrixKit/Controllers/MXKAccountDetailsViewController.m @@ -58,7 +58,6 @@ @interface MXKAccountDetailsViewController () NSMutableArray *linkedEmails; // Dynamic rows in the Linked emails section NSInteger submittedEmailRowIndex; - NSInteger emailTokenRowIndex; // Notifications // Dynamic rows in the Notifications section @@ -349,8 +348,6 @@ - (void)reset submittedEmail = nil; emailSubmitButton = nil; emailTextField = nil; - emailTokenSubmitButton = nil; - emailTokenTextField = nil; [self removeMatrixSession:self.mainSession]; @@ -655,12 +652,69 @@ - (void)dismissMediaPicker } } +- (void)showValidationEmailDialogWithMessage:(NSString*)message +{ + MXKAlert *alert = [[MXKAlert alloc] initWithTitle:[NSBundle mxk_localizedStringForKey:@"account_email_validation_title"] + message:message + style:MXKAlertStyleAlert]; + [alertsArray addObject:alert]; + + alert.cancelButtonIndex = [alert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"abort"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert){ + + emailSubmitButton.enabled = NO; + + }]; + + [alert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"continue"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) { + + [alertsArray removeObject:alert]; + + // We always bind emails when registering, so let's do the same here + [submittedEmail add3PIDToUser:YES success:^{ + + // Add new linked email + if (!linkedEmails) + { + linkedEmails = [NSMutableArray array]; + } + [linkedEmails addObject:submittedEmail.address]; + + // Release pending email and refresh table to remove related cell + emailTextField.text = nil; + submittedEmail = nil; + [self.tableView reloadData]; + + } failure:^(NSError *error) { + + NSLog(@"[MXKAccountDetailsVC] Failed to bind email: %@", error); + + // Display the same popup again if the error is M_THREEPID_AUTH_FAILED + MXError *mxError = [[MXError alloc] initWithNSError:error]; + if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringThreePIDAuthFailed]) + { + [self showValidationEmailDialogWithMessage:[NSBundle mxk_localizedStringForKey:@"account_email_validation_error"]]; + } + else + { + // Notify MatrixKit user + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error]; + } + + // Release the pending email (even if it is Authenticated) + [self.tableView reloadData]; + + }]; + }]; + + [alert showInViewController:self]; +} + #pragma mark - Actions - (IBAction)onButtonPressed:(id)sender { [self dismissKeyboard]; - + if (sender == saveUserInfoButton) { [self saveUserInfo]; @@ -687,81 +741,20 @@ - (IBAction)onButtonPressed:(id)sender } emailSubmitButton.enabled = NO; - [submittedEmail requestValidationTokenWithMatrixRestClient:self.mainSession.matrixRestClient - success:^{ - - // Reset email field - emailTextField.text = nil; - [self.tableView reloadData]; - - } failure:^(NSError *error) { - - NSLog(@"[MXKAccountDetailsVC] Failed to request email token: %@", error); - - // Notify MatrixKit user - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error]; - - emailSubmitButton.enabled = YES; - - }]; - } - else if (sender == emailTokenSubmitButton) - { - emailTokenSubmitButton.enabled = NO; - [submittedEmail validateWithToken:emailTokenTextField.text success:^(BOOL success) { - - if (success) - { - // The email has been "Authenticated" - // Link the email with user's account - [submittedEmail bindWithUserId:_mxAccount.mxCredentials.userId success:^{ - - // Add new linked email - if (!linkedEmails) - { - linkedEmails = [NSMutableArray array]; - } - [linkedEmails addObject:submittedEmail.address]; - - // Release pending email and refresh table to remove related cell - submittedEmail = nil; - [self.tableView reloadData]; - - } failure:^(NSError *error) { - - NSLog(@"[MXKAccountDetailsVC] Failed to link email: %@", error); - - // Notify MatrixKit user - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error]; - - // Release the pending email (even if it is Authenticated) - submittedEmail = nil; - [self.tableView reloadData]; - - }]; - } - else - { - NSLog(@"[MXKAccountDetailsVC] Failed to link email"); - MXKAlert *alert = [[MXKAlert alloc] initWithTitle:nil message:[NSBundle mxk_localizedStringForKey:@"account_error_email_link_failed"] style:MXKAlertStyleAlert]; - [alertsArray addObject:alert]; - alert.cancelButtonIndex = [alert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) { - [alertsArray removeObject:alert]; - }]; - [alert showInViewController:self]; - // Reset wrong token - emailTokenTextField.text = nil; - } - + + [submittedEmail requestValidationTokenWithMatrixRestClient:self.mainSession.matrixRestClient success:^{ + + [self showValidationEmailDialogWithMessage:[NSBundle mxk_localizedStringForKey:@"account_email_validation_message"]]; + } failure:^(NSError *error) { - - NSLog(@"[MXKAccountDetailsVC] Failed to submit email token: %@", error); - + + NSLog(@"[MXKAccountDetailsVC] Failed to request email token: %@", error); + // Notify MatrixKit user [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error]; - - emailTokenSubmitButton.enabled = YES; - + + emailSubmitButton.enabled = YES; + }]; } else if (sender == apnsNotificationsSwitch) @@ -790,10 +783,6 @@ - (void)dismissKeyboard { [emailTextField resignFirstResponder]; } - else if ([emailTokenTextField isFirstResponder]) - { - [emailTokenTextField resignFirstResponder]; - } } #pragma mark - UITextField delegate @@ -837,19 +826,8 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger NSInteger count = 0; if (section == linkedEmailsSection) { - submittedEmailRowIndex = emailTokenRowIndex = -1; - count = linkedEmails.count; submittedEmailRowIndex = count++; - if (submittedEmail && submittedEmail.validationState >= MXK3PIDAuthStateTokenReceived) - { - emailTokenRowIndex = count++; - } - else - { - emailTokenSubmitButton = nil; - emailTokenTextField = nil; - } } else if (section == notificationsSection) { @@ -870,14 +848,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.section == linkedEmailsSection) - { - if (indexPath.row == emailTokenRowIndex) - { - return 70; - } - } - else if (indexPath.section == configurationSection) + if (indexPath.section == configurationSection) { if (indexPath.row == 0) { @@ -936,43 +907,9 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N emailSubmitButton = submittedEmailCell.mxkButton; emailTextField = submittedEmailCell.mxkTextField; - - if (emailTokenRowIndex != -1) - { - // Hide the separator - CGSize screenSize = [[UIScreen mainScreen] bounds].size; - CGFloat rightInset = (screenSize.width < screenSize.height) ? screenSize.height : screenSize.width; - submittedEmailCell.separatorInset = UIEdgeInsetsMake(0.f, 0.f, 0.f, rightInset); - } + cell = submittedEmailCell; } - else if (indexPath.row == emailTokenRowIndex) - { - // Report the current token value (if any) - NSString *currentToken = nil; - if (emailTokenTextField) - { - currentToken = emailTokenTextField.text; - } - - MXKTableViewCellWithLabelTextFieldAndButton *emailTokenCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelTextFieldAndButton defaultReuseIdentifier]]; - if (!emailTokenCell) - { - emailTokenCell = [[MXKTableViewCellWithLabelTextFieldAndButton alloc] init]; - } - - emailTokenCell.mxkLabel.text = [NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"settings_enter_validation_token_for"], submittedEmail.address]; - emailTokenCell.mxkTextField.text = currentToken; - emailTokenCell.mxkButton.enabled = (currentToken.length != 0); - [emailTokenCell.mxkButton setTitle:[NSBundle mxk_localizedStringForKey:@"submit_code"] forState:UIControlStateNormal]; - [emailTokenCell.mxkButton setTitle:[NSBundle mxk_localizedStringForKey:@"submit_code"] forState:UIControlStateHighlighted]; - [emailTokenCell.mxkButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; - - emailTokenSubmitButton = emailTokenCell.mxkButton; - emailTokenTextField = emailTokenCell.mxkTextField; - - cell = emailTokenCell; - } } else if (indexPath.section == notificationsSection) { diff --git a/MatrixKit/Models/MXK3PID.h b/MatrixKit/Models/MXK3PID.h index f3c7d337a..c1499f83f 100644 --- a/MatrixKit/Models/MXK3PID.h +++ b/MatrixKit/Models/MXK3PID.h @@ -20,7 +20,6 @@ typedef enum : NSUInteger { MXK3PIDAuthStateUnknown, MXK3PIDAuthStateTokenRequested, MXK3PIDAuthStateTokenReceived, - MXK3PIDAuthStateTokenSubmitted, MXK3PIDAuthStateAuthenticated } MXK3PIDAuthState; @@ -57,8 +56,8 @@ typedef enum : NSUInteger { /** Start the validation process The identity server will send a validation token to the user's address. - This validation token must be then send back to the identity server with [MXK3PID validateWithToken] - in order to complete the 3PID authentication. + Use the returned sid to complete operations that require authenticated email + like [MXRestClient add3PIDToUser:]. @param restClient used to make matrix API requests during validation process. @param success A block object called when the operation succeeds. @@ -67,29 +66,17 @@ typedef enum : NSUInteger { - (void)requestValidationTokenWithMatrixRestClient:(MXRestClient*)restClient success:(void (^)())success failure:(void (^)(NSError *error))failure; - -/** - Complete the 3rd party id validation by sending the validation token the user received. - - @param validationToken the validation token the user received. - @param success A block object called when the operation succeeds. It indicates if the - validation has succeeded. - @param failure A block object called when the operation fails. - */ -- (void)validateWithToken:(NSString*)validationToken - success:(void (^)(BOOL success))success - failure:(void (^)(NSError *error))failure; - /** Link an authenticated 3rd party id to a Matrix user id. - @param userId the Matrix user id to link the 3PID with. + @param bind bind whether the homeserver should also bind this third party identifier + to the account's Matrix ID with the passed identity server. @param success A block object called when the operation succeeds. It provides the raw server response. @param failure A block object called when the operation fails. */ -- (void)bindWithUserId:(NSString*)userId - success:(void (^)())success - failure:(void (^)(NSError *error))failure; +- (void)add3PIDToUser:(BOOL)bind + success:(void (^)())success + failure:(void (^)(NSError *error))failure; @end \ No newline at end of file diff --git a/MatrixKit/Models/MXK3PID.m b/MatrixKit/Models/MXK3PID.m index a26c69734..db66c5b0b 100644 --- a/MatrixKit/Models/MXK3PID.m +++ b/MatrixKit/Models/MXK3PID.m @@ -32,7 +32,6 @@ - (instancetype)initWithMedium:(NSString *)medium andAddress:(NSString *)address { self = [super init]; if (self) - { _medium = [medium copy]; _address = [address copy]; @@ -117,120 +116,42 @@ - (void)requestValidationTokenWithMatrixRestClient:(MXRestClient*)restClient } } -- (void)validateWithToken:(NSString*)validationToken - success:(void (^)(BOOL success))success - failure:(void (^)(NSError *error))failure +- (void)add3PIDToUser:(BOOL)bind + success:(void (^)())success + failure:(void (^)(NSError *error))failure { - // Sanity check - if (_validationState == MXK3PIDAuthStateTokenReceived) + if ([self.medium isEqualToString:kMX3PIDMediumEmail]) { - - if ([self.medium isEqualToString:kMX3PIDMediumEmail]) - { - _validationState = MXK3PIDAuthStateTokenSubmitted; - - currentRequest = [mxRestClient validateEmail:self.sid validationToken:validationToken clientSecret:self.clientSecret success:^(BOOL successFlag) + currentRequest = [mxRestClient add3PID:self.sid clientSecret:self.clientSecret bind:bind success:^{ + // Update linked userId in 3PID + self.userId = mxRestClient.credentials.userId; + currentRequest = nil; + + if (success) { - if (successFlag) - { - // Validation is complete - _validationState = MXK3PIDAuthStateAuthenticated; - } - else - { - // Return in previous step - _validationState = MXK3PIDAuthStateTokenReceived; - } - - currentRequest = nil; - - if (success) - { - success(successFlag); - } - } failure:^(NSError *error) + success(); + } + } failure:^(NSError *error) { + currentRequest = nil; + + if (failure) { - // Return in previous step - _validationState = MXK3PIDAuthStateTokenReceived; - currentRequest = nil; - - if (failure) - { - failure (error); - } - }]; - - return; - } - else if ([self.medium isEqualToString:kMX3PIDMediumMSISDN]) - { - // FIXME: support msisdn as soon as identity server supports it - NSLog(@"[MXK3PID] validateWithToken: is not supported for this 3PID: %@ (%@)", self.address, self.medium); - } - else - { - NSLog(@"[MXK3PID] validateWithToken: is not supported for this 3PID: %@ (%@)", self.address, self.medium); - } - } - else - { - NSLog(@"[MXK3PID] Failed to valid with token 3PID: %@ (%@), state: %lu", self.address, self.medium, (unsigned long)_validationState); - } - - // Here the validation process failed - if (failure) - { - failure (nil); - } -} + failure (error); + } + }]; -- (void)bindWithUserId:(NSString*)userId - success:(void (^)())success - failure:(void (^)(NSError *error))failure -{ - // Sanity check - if (_validationState == MXK3PIDAuthStateAuthenticated) + return; + } + else if ([self.medium isEqualToString:kMX3PIDMediumMSISDN]) { - - if ([self.medium isEqualToString:kMX3PIDMediumEmail]) - { - currentRequest = [mxRestClient bind3PID:userId sid:self.sid clientSecret:self.clientSecret success:^(NSDictionary *JSONResponse) - { - // Update linked userId in 3PID - self.userId = userId; - currentRequest = nil; - - if (success) - { - success(); - } - } failure:^(NSError *error) - { - currentRequest = nil; - - if (failure) - { - failure (error); - } - }]; - - return; - } - else if ([self.medium isEqualToString:kMX3PIDMediumMSISDN]) - { - // FIXME: support msisdn as soon as identity server supports it - NSLog(@"[MXK3PID] bindWithUserId: is not supported for this 3PID: %@ (%@)", self.address, self.medium); - } - else - { - NSLog(@"[MXK3PID] bindWithUserId: is not supported for this 3PID: %@ (%@)", self.address, self.medium); - } + // FIXME: support msisdn as soon as identity server supports it + NSLog(@"[MXK3PID] bindWithUserId: is not supported for this 3PID: %@ (%@)", self.address, self.medium); } else { - NSLog(@"[MXK3PID] Failed to bind 3PID: %@ (%@), state: %lu", self.address, self.medium, (unsigned long)_validationState); + NSLog(@"[MXK3PID] bindWithUserId: is not supported for this 3PID: %@ (%@)", self.address, self.medium); } - + // Here the validation process failed if (failure) { From 642b2b8860ce728ede97fd88235efb3a7a2d1d70 Mon Sep 17 00:00:00 2001 From: giomfo Date: Mon, 4 Apr 2016 13:35:12 +0200 Subject: [PATCH 15/30] Registration support: Check whether user id is already use. --- .../MXKAuthenticationViewController.m | 122 ++++++++++++------ .../MXKAuthInputsEmailCodeBasedView.m | 5 + .../MXKAuthInputsPasswordBasedView.m | 5 + .../Views/Authentication/MXKAuthInputsView.h | 5 + .../Views/Authentication/MXKAuthInputsView.m | 5 + 5 files changed, 103 insertions(+), 39 deletions(-) diff --git a/MatrixKit/Controllers/MXKAuthenticationViewController.m b/MatrixKit/Controllers/MXKAuthenticationViewController.m index e6e0961cb..985ff387b 100644 --- a/MatrixKit/Controllers/MXKAuthenticationViewController.m +++ b/MatrixKit/Controllers/MXKAuthenticationViewController.m @@ -582,55 +582,69 @@ - (IBAction)onButtonPressed:(id)sender self.userInteractionEnabled = NO; [self.authInputsContainerView bringSubviewToFront: _authenticationActivityIndicator]; - // Launch authentication by preparing parameters dict - [self.authInputsView prepareParameters:^(NSDictionary *parameters) { - - if (parameters && mxRestClient) - { - [_authenticationActivityIndicator startAnimating]; + // Launch the authentication according to its type + if (_authType == MXKAuthenticationTypeLogin) + { + // Prepare the parameters dict + [self.authInputsView prepareParameters:^(NSDictionary *parameters) { - if (_authType == MXKAuthenticationTypeLogin) + if (parameters && mxRestClient) { - mxCurrentOperation = [mxRestClient login:parameters success:^(NSDictionary *JSONResponse) { - - MXCredentials *credentials = [MXCredentials modelFromJSON:JSONResponse]; - - // Sanity check - if (!credentials.userId || !credentials.accessToken) - { - [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; - } - else - { - NSLog(@"[MXKAuthenticationVC] Login process succeeded"); - - // Workaround: HS does not return the right URL. Use the one we used to make the request - credentials.homeServer = mxRestClient.homeserver; - // Report the certificate trusted by user (if any) - credentials.allowedCertificate = mxRestClient.allowedCertificate; - - [self onSuccessfulLogin:credentials]; - } - - } failure:^(NSError *error) { - - [self onFailureDuringAuthRequest:error]; - - }]; + [_authenticationActivityIndicator startAnimating]; + [self loginWithParameters:parameters]; } else { - [self registerWithParameters:parameters]; + NSLog(@"[MXKAuthenticationVC] Failed to prepare parameters"); + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; } + + }]; + } + else + { + // Check first the availability of the userId + if (self.authInputsView.userId.length) + { + [_authenticationActivityIndicator startAnimating]; + + [mxRestClient isUserNameInUse:self.authInputsView.userId callback:^(BOOL isUserNameInUse) { + + [_authenticationActivityIndicator stopAnimating]; + + if (isUserNameInUse) + { + NSLog(@"[MXKAuthenticationVC] User name is already use"); + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"auth_username_in_use"]}]]; + } + else + { + // Launch registration by preparing parameters dict + [self.authInputsView prepareParameters:^(NSDictionary *parameters) { + + if (parameters && mxRestClient) + { + [_authenticationActivityIndicator startAnimating]; + [self registerWithParameters:parameters]; + } + else + { + NSLog(@"[MXKAuthenticationVC] Failed to prepare parameters"); + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; + } + + }]; + } + + }]; + } else { - NSLog(@"[MXKAuthenticationVC] Failed to prepare parameters"); - - [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; + NSLog(@"[MXKAuthenticationVC] User name is missing"); + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"auth_invalid_user_name"]}]]; } - - }]; + } } else if (sender == _authSwitchButton) { @@ -737,6 +751,36 @@ - (void)updateRESTClient } } +- (void)loginWithParameters:(NSDictionary*)parameters +{ + mxCurrentOperation = [mxRestClient login:parameters success:^(NSDictionary *JSONResponse) { + + MXCredentials *credentials = [MXCredentials modelFromJSON:JSONResponse]; + + // Sanity check + if (!credentials.userId || !credentials.accessToken) + { + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; + } + else + { + NSLog(@"[MXKAuthenticationVC] Login process succeeded"); + + // Workaround: HS does not return the right URL. Use the one we used to make the request + credentials.homeServer = mxRestClient.homeserver; + // Report the certificate trusted by user (if any) + credentials.allowedCertificate = mxRestClient.allowedCertificate; + + [self onSuccessfulLogin:credentials]; + } + + } failure:^(NSError *error) { + + [self onFailureDuringAuthRequest:error]; + + }]; +} + - (void)registerWithParameters:(NSDictionary*)parameters { mxCurrentOperation = [mxRestClient registerWithParameters:parameters success:^(NSDictionary *JSONResponse) { diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m b/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m index 739685077..e87381a7d 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m @@ -102,6 +102,11 @@ - (void)nextStep self.displayNameTextField.hidden = YES; } +- (NSString*)userId +{ + return self.userLoginTextField.text; +} + #pragma mark UITextField delegate - (BOOL)textFieldShouldReturn:(UITextField*)textField diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m b/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m index b0a292287..f8aa7ae2d 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m @@ -130,6 +130,11 @@ - (void)dismissKeyboard [super dismissKeyboard]; } +- (NSString*)userId +{ + return self.userLoginTextField.text; +} + #pragma mark UITextField delegate - (BOOL)textFieldShouldReturn:(UITextField*)textField diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsView.h b/MatrixKit/Views/Authentication/MXKAuthInputsView.h index 28d8cd773..da03981a3 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsView.h +++ b/MatrixKit/Views/Authentication/MXKAuthInputsView.h @@ -91,6 +91,11 @@ typedef enum { */ @property (nonatomic, readonly) MXAuthenticationSession *authSession; +/** + The current filled user identifier (nil by default). + */ +@property (nonatomic, readonly) NSString *userId; + /** The layout constraint defined on the view height. This height takes into account shown/hidden fields. */ diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsView.m b/MatrixKit/Views/Authentication/MXKAuthInputsView.m index 2216c24c4..d256ddffa 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsView.m @@ -131,4 +131,9 @@ - (MXAuthenticationSession*)authSession return currentSession; } +- (NSString*)userId +{ + return nil; +} + @end \ No newline at end of file From 4f164d7e6922ae18216b73ddb140117f58d2821d Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 4 Apr 2016 14:20:06 +0200 Subject: [PATCH 16/30] New email binding: Load linked emails from the hs --- .../MXKAccountDetailsViewController.m | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/MatrixKit/Controllers/MXKAccountDetailsViewController.m b/MatrixKit/Controllers/MXKAccountDetailsViewController.m index dbe2df161..d89f7bbc1 100644 --- a/MatrixKit/Controllers/MXKAccountDetailsViewController.m +++ b/MatrixKit/Controllers/MXKAccountDetailsViewController.m @@ -189,7 +189,10 @@ - (void)setMxAccount:(MXKAccount *)account currentDisplayName = _mxAccount.userDisplayName; self.userDisplayName.text = currentDisplayName; [self updateSaveUserInfoButtonStatus]; - + + // Load linked emails + [self loadLinkedEmails]; + // Add observer on user's information accountUserInfoObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountUserInfoDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { // Ignore any refresh when saving is in progress @@ -672,17 +675,12 @@ - (void)showValidationEmailDialogWithMessage:(NSString*)message // We always bind emails when registering, so let's do the same here [submittedEmail add3PIDToUser:YES success:^{ - // Add new linked email - if (!linkedEmails) - { - linkedEmails = [NSMutableArray array]; - } - [linkedEmails addObject:submittedEmail.address]; - // Release pending email and refresh table to remove related cell emailTextField.text = nil; submittedEmail = nil; - [self.tableView reloadData]; + + // Update linked emails + [self loadLinkedEmails]; } failure:^(NSError *error) { @@ -709,6 +707,35 @@ - (void)showValidationEmailDialogWithMessage:(NSString*)message [alert showInViewController:self]; } +- (void)loadLinkedEmails +{ + if (!linkedEmails) + { + linkedEmails = [NSMutableArray array]; + } + + [_mxAccount.mxRestClient threePIDs:^(NSArray *threePIDs) { + + if (linkedEmails) + { + [linkedEmails removeAllObjects]; + + for (MXThirdPartyIdentifier *threePID in threePIDs) + { + if ([threePID.medium isEqualToString:kMX3PIDMediumEmail]) + { + [linkedEmails addObject:threePID.address]; + } + } + + [self.tableView reloadData]; + } + + } failure:^(NSError *error) { + // Silently fail + }]; +} + #pragma mark - Actions - (IBAction)onButtonPressed:(id)sender From dcb9149959d3cce1621f5dc503abe48b3a6e7802 Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 4 Apr 2016 14:48:15 +0200 Subject: [PATCH 17/30] New email binding: Validate email client side before doing a request to the hs --- .../en.lproj/MatrixKit.strings | 4 +++- .../Controllers/MXKAccountDetailsViewController.m | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings b/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings index 2d04c1a03..2aa87097a 100644 --- a/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings +++ b/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings @@ -117,14 +117,16 @@ "account_save_changes" = "Save changes"; "account_link_email" = "Link Email"; "account_linked_emails" = "Linked emails"; + "account_email_validation_title" = "Verification Pending"; "account_email_validation_message" = "Please check your email and click on the link it contains. Once this is done, click continue."; "account_email_validation_error" = "Unable to verify email address. Please check your email and click on the link it contains. Once this is done, click continue"; "account_error_display_name_change_failed" = "Display name change failed"; "account_error_picture_change_failed" = "Picture change failed"; -"account_error_email_link_failed" = "Failed to link email"; "account_error_matrix_session_is_not_opened" = "Matrix session is not opened"; +"account_error_email_wrong_title" = "Invalid Email Address"; +"account_error_email_wrong_description" = "This doesn't appear to be a valid email address"; // Room creation "room_creation_name_title" = "Room name:"; diff --git a/MatrixKit/Controllers/MXKAccountDetailsViewController.m b/MatrixKit/Controllers/MXKAccountDetailsViewController.m index d89f7bbc1..cc8975091 100644 --- a/MatrixKit/Controllers/MXKAccountDetailsViewController.m +++ b/MatrixKit/Controllers/MXKAccountDetailsViewController.m @@ -762,6 +762,20 @@ - (IBAction)onButtonPressed:(id)sender } else if (sender == emailSubmitButton) { + // Email check + if (![MXTools isEmailAddress:emailTextField.text]) + { + MXKAlert *alert = [[MXKAlert alloc] initWithTitle:[NSBundle mxk_localizedStringForKey:@"account_error_email_wrong_title"] message:[NSBundle mxk_localizedStringForKey:@"account_error_email_wrong_description"] style:MXKAlertStyleAlert]; + [alertsArray addObject:alert]; + + alert.cancelButtonIndex = [alert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) { + [alertsArray removeObject:alert]; + }]; + [alert showInViewController:self]; + + return; + } + if (!submittedEmail || ![submittedEmail.address isEqualToString:emailTextField.text]) { submittedEmail = [[MXK3PID alloc] initWithMedium:kMX3PIDMediumEmail andAddress:emailTextField.text]; From fd2d1f5088e1a622071233ad534f7c58e8b80ce1 Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 4 Apr 2016 14:54:42 +0200 Subject: [PATCH 18/30] New email binding: MXKAccountDetailsVC: Use an email keyboard --- MatrixKit/Controllers/MXKAccountDetailsViewController.m | 1 + 1 file changed, 1 insertion(+) diff --git a/MatrixKit/Controllers/MXKAccountDetailsViewController.m b/MatrixKit/Controllers/MXKAccountDetailsViewController.m index cc8975091..cd2576472 100644 --- a/MatrixKit/Controllers/MXKAccountDetailsViewController.m +++ b/MatrixKit/Controllers/MXKAccountDetailsViewController.m @@ -941,6 +941,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } submittedEmailCell.mxkTextField.text = currentEmail; + submittedEmailCell.mxkTextField.keyboardType = UIKeyboardTypeEmailAddress; submittedEmailCell.mxkButton.enabled = (currentEmail.length != 0); [submittedEmailCell.mxkButton setTitle:[NSBundle mxk_localizedStringForKey:@"account_link_email"] forState:UIControlStateNormal]; [submittedEmailCell.mxkButton setTitle:[NSBundle mxk_localizedStringForKey:@"account_link_email"] forState:UIControlStateHighlighted]; From 12b2b42aa25c20bf8b6f902fd24c8346721ed61c Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 4 Apr 2016 15:21:18 +0200 Subject: [PATCH 19/30] New email binding: MXK3PID: Keep MXK3PIDAuthStateTokenSubmitted state. It may be useful to validate other 3PID than email --- MatrixKit/Models/MXK3PID.h | 1 + 1 file changed, 1 insertion(+) diff --git a/MatrixKit/Models/MXK3PID.h b/MatrixKit/Models/MXK3PID.h index 1f30d9467..d736f23b3 100644 --- a/MatrixKit/Models/MXK3PID.h +++ b/MatrixKit/Models/MXK3PID.h @@ -20,6 +20,7 @@ typedef enum : NSUInteger { MXK3PIDAuthStateUnknown, MXK3PIDAuthStateTokenRequested, MXK3PIDAuthStateTokenReceived, + MXK3PIDAuthStateTokenSubmitted, MXK3PIDAuthStateAuthenticated } MXK3PIDAuthState; From 85b4fe35e93cf62c3e3d1b3212a35195fc275057 Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 4 Apr 2016 15:36:15 +0200 Subject: [PATCH 20/30] New email binding: MXK3PID: Improved comments --- MatrixKit/Models/MXK3PID.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MatrixKit/Models/MXK3PID.h b/MatrixKit/Models/MXK3PID.h index d736f23b3..16213aa90 100644 --- a/MatrixKit/Models/MXK3PID.h +++ b/MatrixKit/Models/MXK3PID.h @@ -67,8 +67,8 @@ typedef enum : NSUInteger { /** Start the validation process The identity server will send a validation token to the user's address. - Use the returned sid to complete operations that require authenticated email - like [MXRestClient add3PIDToUser:]. + In case of email, the end user must click on the link in the received email + to validate their email address in order to be able to call add3PIDToUser successfully. @param restClient used to make matrix API requests during validation process. @param success A block object called when the operation succeeds. @@ -78,10 +78,10 @@ typedef enum : NSUInteger { success:(void (^)())success failure:(void (^)(NSError *error))failure; /** - Link an authenticated 3rd party id to a Matrix user id. + Link a 3rd party id to the user. - @param bind bind whether the homeserver should also bind this third party identifier - to the account's Matrix ID with the passed identity server. + @param bind whether the homeserver should also bind this third party identifier + to the account's Matrix ID with the identity server. @param success A block object called when the operation succeeds. It provides the raw server response. @param failure A block object called when the operation fails. From 32a63516039660495e53ab87b6524b0afa96c83b Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 4 Apr 2016 15:43:08 +0200 Subject: [PATCH 21/30] New email binding: MXK3PID: Improved comments --- MatrixKit/Models/MXK3PID.h | 1 + 1 file changed, 1 insertion(+) diff --git a/MatrixKit/Models/MXK3PID.h b/MatrixKit/Models/MXK3PID.h index 16213aa90..49ec28284 100644 --- a/MatrixKit/Models/MXK3PID.h +++ b/MatrixKit/Models/MXK3PID.h @@ -67,6 +67,7 @@ typedef enum : NSUInteger { /** Start the validation process The identity server will send a validation token to the user's address. + In case of email, the end user must click on the link in the received email to validate their email address in order to be able to call add3PIDToUser successfully. From cf84aaf9ffd9eecbf464f513f9c2457063c59520 Mon Sep 17 00:00:00 2001 From: giomfo Date: Mon, 4 Apr 2016 17:03:54 +0200 Subject: [PATCH 22/30] Registration support: Check parameters --- .../en.lproj/MatrixKit.strings | 2 + .../MXKAuthenticationViewController.m | 129 +++++++++++------- .../MXKAuthInputsEmailCodeBasedView.m | 20 ++- .../MXKAuthInputsPasswordBasedView.m | 24 +++- .../Views/Authentication/MXKAuthInputsView.h | 11 +- .../Views/Authentication/MXKAuthInputsView.m | 8 +- 6 files changed, 135 insertions(+), 59 deletions(-) diff --git a/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings b/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings index bcdfdc9fb..f36a8c1af 100644 --- a/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings +++ b/MatrixKit/Assets/MatrixKitAssets.bundle/en.lproj/MatrixKit.strings @@ -45,6 +45,8 @@ "login_error_login_email_not_yet" = "The email link which has not been clicked yet"; "login_use_fallback" = "Use fallback page"; "login_leave_fallback" = "Cancel"; +"login_invalid_param" = "Invalid parameter"; +"register_error_title" = "Registration Failed"; // Action "no" = "No"; diff --git a/MatrixKit/Controllers/MXKAuthenticationViewController.m b/MatrixKit/Controllers/MXKAuthenticationViewController.m index 985ff387b..9b69bd114 100644 --- a/MatrixKit/Controllers/MXKAuthenticationViewController.m +++ b/MatrixKit/Controllers/MXKAuthenticationViewController.m @@ -434,7 +434,7 @@ - (void)setDefaultIdentityServerUrl:(NSString *)defaultIdentityServerUrl - (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled { - _submitButton.enabled = (userInteractionEnabled && _authInputsView.areAllRequiredFieldsFilled); + _submitButton.enabled = (userInteractionEnabled && _authInputsView.areAllRequiredFieldsSet); _authSwitchButton.enabled = userInteractionEnabled; _homeServerTextField.enabled = userInteractionEnabled; @@ -580,69 +580,79 @@ - (IBAction)onButtonPressed:(id)sender { // Disable user interaction to prevent multiple requests self.userInteractionEnabled = NO; - [self.authInputsContainerView bringSubviewToFront: _authenticationActivityIndicator]; - // Launch the authentication according to its type - if (_authType == MXKAuthenticationTypeLogin) + // Check parameters validity + NSString *errorMsg = [self.authInputsView validateParameters]; + if (errorMsg) { - // Prepare the parameters dict - [self.authInputsView prepareParameters:^(NSDictionary *parameters) { - - if (parameters && mxRestClient) - { - [_authenticationActivityIndicator startAnimating]; - [self loginWithParameters:parameters]; - } - else - { - NSLog(@"[MXKAuthenticationVC] Failed to prepare parameters"); - [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; - } - - }]; + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:errorMsg}]]; } else { - // Check first the availability of the userId - if (self.authInputsView.userId.length) + [self.authInputsContainerView bringSubviewToFront: _authenticationActivityIndicator]; + + // Launch the authentication according to its type + if (_authType == MXKAuthenticationTypeLogin) { - [_authenticationActivityIndicator startAnimating]; - - [mxRestClient isUserNameInUse:self.authInputsView.userId callback:^(BOOL isUserNameInUse) { + // Prepare the parameters dict + [self.authInputsView prepareParameters:^(NSDictionary *parameters) { - [_authenticationActivityIndicator stopAnimating]; - - if (isUserNameInUse) + if (parameters && mxRestClient) { - NSLog(@"[MXKAuthenticationVC] User name is already use"); - [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"auth_username_in_use"]}]]; + [_authenticationActivityIndicator startAnimating]; + [self loginWithParameters:parameters]; } else { - // Launch registration by preparing parameters dict - [self.authInputsView prepareParameters:^(NSDictionary *parameters) { - - if (parameters && mxRestClient) - { - [_authenticationActivityIndicator startAnimating]; - [self registerWithParameters:parameters]; - } - else - { - NSLog(@"[MXKAuthenticationVC] Failed to prepare parameters"); - [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; - } - - }]; + NSLog(@"[MXKAuthenticationVC] Failed to prepare parameters"); + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; } }]; - } else { - NSLog(@"[MXKAuthenticationVC] User name is missing"); - [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"auth_invalid_user_name"]}]]; + // Check here the availability of the userId + if (self.authInputsView.userId.length) + { + [_authenticationActivityIndicator startAnimating]; + + [mxRestClient isUserNameInUse:self.authInputsView.userId callback:^(BOOL isUserNameInUse) { + + [_authenticationActivityIndicator stopAnimating]; + + if (isUserNameInUse) + { + NSLog(@"[MXKAuthenticationVC] User name is already use"); + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"auth_username_in_use"]}]]; + } + else + { + // Launch registration by preparing parameters dict + [self.authInputsView prepareParameters:^(NSDictionary *parameters) { + + if (parameters && mxRestClient) + { + [_authenticationActivityIndicator startAnimating]; + [self registerWithParameters:parameters]; + } + else + { + NSLog(@"[MXKAuthenticationVC] Failed to prepare parameters"); + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; + } + + }]; + } + + }]; + + } + else + { + NSLog(@"[MXKAuthenticationVC] User name is missing"); + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"auth_invalid_user_name"]}]]; + } } } } @@ -964,7 +974,19 @@ - (void)onFailureDuringAuthRequest:(NSError *)error NSLog(@"[MXKAuthenticationVC] Auth request failed: %@", error); - // translate the error code to a human message + // Translate the error code to a human message + NSString *title = error.localizedFailureReason; + if (!title) + { + if (self.authType == MXKAuthenticationTypeLogin) + { + title = [NSBundle mxk_localizedStringForKey:@"login_error_title"]; + } + else + { + title = [NSBundle mxk_localizedStringForKey:@"register_error_title"]; + } + } NSString* message = error.localizedDescription; NSDictionary* dict = error.userInfo; @@ -1016,8 +1038,13 @@ - (void)onFailureDuringAuthRequest:(NSError *)error } // Alert user - alert = [[MXKAlert alloc] initWithTitle:[NSBundle mxk_localizedStringForKey:@"login_error_title"] message:message style:MXKAlertStyleAlert]; - [alert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"dismiss"] style:MXKAlertActionStyleCancel handler:^(MXKAlert *alert) + if (alert) + { + [alert dismiss:NO]; + } + + alert = [[MXKAlert alloc] initWithTitle:title message:message style:MXKAlertStyleAlert]; + [alert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:MXKAlertActionStyleCancel handler:^(MXKAlert *alert) {}]; [alert showInViewController:self]; @@ -1072,7 +1099,7 @@ - (void)dismissKeyboard - (void)onTextFieldChange:(NSNotification *)notif { - _submitButton.enabled = _authInputsView.areAllRequiredFieldsFilled; + _submitButton.enabled = _authInputsView.areAllRequiredFieldsSet; } - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m b/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m index e87381a7d..d6ae10c59 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.m @@ -71,12 +71,28 @@ - (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKA return NO; } -- (BOOL)areAllRequiredFieldsFilled +- (NSString*)validateParameters { - BOOL ret = [super areAllRequiredFieldsFilled]; + NSString *errorMsg = [super validateParameters]; + + if (!errorMsg) + { + if (!self.areAllRequiredFieldsSet) + { + errorMsg = [NSBundle mxk_localizedStringForKey:@"login_invalid_param"]; + } + } + + return errorMsg; +} + +- (BOOL)areAllRequiredFieldsSet +{ + BOOL ret = [super areAllRequiredFieldsSet]; // Check required fields //FIXME what are required fields in this authentication flow? ret = (ret && self.userLoginTextField.text.length && self.emailAndTokenTextField.text.length); + return ret; } diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m b/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m index f8aa7ae2d..e139b9735 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m @@ -72,14 +72,31 @@ - (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKA return NO; } +- (NSString*)validateParameters +{ + NSString *errorMsg = [super validateParameters]; + + if (!errorMsg) + { + // Check user login and pass fields + if (!self.areAllRequiredFieldsSet) + { + errorMsg = [NSBundle mxk_localizedStringForKey:@"login_invalid_param"]; + } + } + + return errorMsg; +} + - (void)prepareParameters:(void (^)(NSDictionary *parameters))callback; { if (callback) { // Sanity check on required fields - if (self.areAllRequiredFieldsFilled == NO) + if (!self.areAllRequiredFieldsSet) { callback(nil); + return; } // Retrieve the user login and check whether it is an email or a username. @@ -111,12 +128,13 @@ - (void)prepareParameters:(void (^)(NSDictionary *parameters))callback; } } -- (BOOL)areAllRequiredFieldsFilled +- (BOOL)areAllRequiredFieldsSet { - BOOL ret = [super areAllRequiredFieldsFilled]; + BOOL ret = [super areAllRequiredFieldsSet]; // Check user login and pass fields ret = (ret && self.userLoginTextField.text.length && self.passWordTextField.text.length); + return ret; } diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsView.h b/MatrixKit/Views/Authentication/MXKAuthInputsView.h index da03981a3..f264b820f 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsView.h +++ b/MatrixKit/Views/Authentication/MXKAuthInputsView.h @@ -129,6 +129,13 @@ typedef enum { */ - (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKAuthenticationType)authType; +/** + Check the validity of the required parameters. + + @return an error message in case of wrong parameters (nil by default). + */ +- (NSString*)validateParameters; + /** Prepare the set of the inputs in order to launch an authentication process. @@ -147,9 +154,9 @@ typedef enum { - (void)updateAuthSessionWithCompletedStages:(NSArray *)completedStages didUpdateParameters:(void (^)(NSDictionary *parameters))callback; /** - YES when all required fields are filled. + Tell whether all required fields are set */ -- (BOOL)areAllRequiredFieldsFilled; +- (BOOL)areAllRequiredFieldsSet; /** Tell whether the email field is empty while the email binding is supported. diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsView.m b/MatrixKit/Views/Authentication/MXKAuthInputsView.m index d256ddffa..80665145c 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsView.m @@ -71,6 +71,12 @@ - (BOOL)setAuthSession:(MXAuthenticationSession *)authSession withAuthType:(MXKA return NO; } +- (NSString *)validateParameters +{ + // Currently no field to check here + return nil; +} + - (void)prepareParameters:(void (^)(NSDictionary *parameters))callback { // Do nothing by default @@ -89,7 +95,7 @@ - (void)updateAuthSessionWithCompletedStages:(NSArray *)completedStages didUpdat } } -- (BOOL)areAllRequiredFieldsFilled +- (BOOL)areAllRequiredFieldsSet { // Currently no field to check here return YES; From 6b7f1197e7fb2c28004501b6027461173e32381e Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 4 Apr 2016 17:40:45 +0200 Subject: [PATCH 23/30] New email binding: Store (permanently) 3PIDs in MXKAccount --- .../MXKAccountDetailsViewController.m | 38 +++---------- MatrixKit/Models/Account/MXKAccount.h | 21 +++++++ MatrixKit/Models/Account/MXKAccount.m | 57 ++++++++++++++++++- 3 files changed, 85 insertions(+), 31 deletions(-) diff --git a/MatrixKit/Controllers/MXKAccountDetailsViewController.m b/MatrixKit/Controllers/MXKAccountDetailsViewController.m index cd2576472..8a2cf823f 100644 --- a/MatrixKit/Controllers/MXKAccountDetailsViewController.m +++ b/MatrixKit/Controllers/MXKAccountDetailsViewController.m @@ -52,10 +52,7 @@ @interface MXKAccountDetailsViewController () // account user's profile observer id accountUserInfoObserver; - - // Linked emails - // TODO: When server will provide existing linked emails, these linked emails should be stored in MXKAccount instance. - NSMutableArray *linkedEmails; + // Dynamic rows in the Linked emails section NSInteger submittedEmailRowIndex; @@ -347,7 +344,6 @@ - (void)reset saveUserInfoButton.enabled = NO; - linkedEmails = nil; submittedEmail = nil; emailSubmitButton = nil; emailTextField = nil; @@ -709,30 +705,14 @@ - (void)showValidationEmailDialogWithMessage:(NSString*)message - (void)loadLinkedEmails { - if (!linkedEmails) - { - linkedEmails = [NSMutableArray array]; - } - - [_mxAccount.mxRestClient threePIDs:^(NSArray *threePIDs) { - - if (linkedEmails) - { - [linkedEmails removeAllObjects]; - - for (MXThirdPartyIdentifier *threePID in threePIDs) - { - if ([threePID.medium isEqualToString:kMX3PIDMediumEmail]) - { - [linkedEmails addObject:threePID.address]; - } - } + // Refresh the account 3PIDs list + [_mxAccount load3PIDs:^{ - [self.tableView reloadData]; - } + [self.tableView reloadData]; } failure:^(NSError *error) { - // Silently fail + // Display the data that has been loaded last time + [self.tableView reloadData]; }]; } @@ -867,7 +847,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger NSInteger count = 0; if (section == linkedEmailsSection) { - count = linkedEmails.count; + count = _mxAccount.linkedEmails.count; submittedEmailRowIndex = count++; } else if (section == notificationsSection) @@ -914,7 +894,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N if (indexPath.section == linkedEmailsSection) { - if (indexPath.row < linkedEmails.count) + if (indexPath.row < _mxAccount.linkedEmails.count) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kMXKAccountDetailsLinkedEmailCellId]; if (!cell) @@ -923,7 +903,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } cell.selectionStyle = UITableViewCellSelectionStyleNone; - cell.textLabel.text = [linkedEmails objectAtIndex:indexPath.row]; + cell.textLabel.text = [_mxAccount.linkedEmails objectAtIndex:indexPath.row]; } else if (indexPath.row == submittedEmailRowIndex) { diff --git a/MatrixKit/Models/Account/MXKAccount.h b/MatrixKit/Models/Account/MXKAccount.h index 3ab32d8c3..123f9755e 100644 --- a/MatrixKit/Models/Account/MXKAccount.h +++ b/MatrixKit/Models/Account/MXKAccount.h @@ -93,6 +93,18 @@ typedef BOOL (^MXKAccountOnCertificateChange)(MXKAccount *mxAccount, NSData *cer */ @property (nonatomic, readonly) NSString *fullDisplayName; +/** + The 3PIDs linked to this account. + [self load3PIDs] must be called to update the property. + */ +@property (nonatomic, readonly) NSArray *threePIDs; + +/** + The email addresses linked to this account. + This is a subset of self.threePIDs. + */ +@property (nonatomic, readonly) NSArray *linkedEmails; + /** The account user's presence (`MXPresenceUnknown` by default, available if matrix session `mxSession` is opened). The notification `kMXKAccountUserInfoDidChangeNotification` is posted in case of change of this property. @@ -240,6 +252,15 @@ typedef BOOL (^MXKAccountOnCertificateChange)(MXKAccount *mxAccount, NSData *cer */ - (void)changePassword:(NSString*)oldPassword with:(NSString*)newPassword success:(void (^)())success failure:(void (^)(NSError *error))failure; +/** + Load the 3PIDs linked to this account. + This method must be called to refresh self.threePIDs and self.linkedEmails. + + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. + */ +- (void)load3PIDs:(void (^)())success failure:(void (^)(NSError *error))failure; + #pragma mark - Push notification listeners /** Register a listener to push notifications for the account's session. diff --git a/MatrixKit/Models/Account/MXKAccount.m b/MatrixKit/Models/Account/MXKAccount.m index 8b63c70d3..47044e809 100644 --- a/MatrixKit/Models/Account/MXKAccount.m +++ b/MatrixKit/Models/Account/MXKAccount.m @@ -76,6 +76,7 @@ @interface MXKAccount () @implementation MXKAccount @synthesize mxCredentials, mxSession, mxRestClient; +@synthesize threePIDs; @synthesize userPresence; @synthesize userTintColor; @synthesize hideUserPresence; @@ -148,7 +149,12 @@ - (id)initWithCoder:(NSCoder *)coder mxCredentials.allowedCertificate = [coder decodeObjectForKey:@"allowedCertificate"]; [self prepareRESTClient]; - + + if ([coder decodeObjectForKey:@"threePIDs"]) + { + threePIDs = [coder decodeObjectForKey:@"threePIDs"]; + } + userPresence = MXPresenceUnknown; if ([coder decodeObjectForKey:@"identityserverurl"]) @@ -185,7 +191,12 @@ - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:mxCredentials.allowedCertificate forKey:@"allowedCertificate"]; } - + + if (self.threePIDs) + { + [coder encodeObject:threePIDs forKey:@"threePIDs"]; + } + if (self.identityServerURL) { [coder encodeObject:_identityServerURL forKey:@"identityserverurl"]; @@ -261,6 +272,26 @@ - (NSString*)fullDisplayName } } +- (NSArray *)threePIDs +{ + return threePIDs; +} + +- (NSArray *)linkedEmails +{ + NSMutableArray *linkedEmails = [NSMutableArray array]; + + for (MXThirdPartyIdentifier *threePID in threePIDs) + { + if ([threePID.medium isEqualToString:kMX3PIDMediumEmail]) + { + [linkedEmails addObject:threePID.address]; + } + } + + return linkedEmails; +} + - (UIColor*)userTintColor { if (!userTintColor) @@ -391,6 +422,28 @@ - (void)changePassword:(NSString*)oldPassword with:(NSString*)newPassword succes } } +- (void)load3PIDs:(void (^)())success failure:(void (^)(NSError *))failure +{ + [mxRestClient threePIDs:^(NSArray *threePIDs2) { + + threePIDs = threePIDs2; + + // Archive updated field + [[MXKAccountManager sharedManager] saveAccounts]; + + if (success) + { + success(); + } + + } failure:^(NSError *error) { + if (failure) + { + failure(error); + } + }]; +} + - (void)setUserPresence:(MXPresence)presence andStatusMessage:(NSString *)statusMessage completion:(void (^)(void))completion { userPresence = presence; From df01e55e2b0f70fb7876994b652002479418c132 Mon Sep 17 00:00:00 2001 From: giomfo Date: Mon, 4 Apr 2016 21:21:05 +0200 Subject: [PATCH 24/30] MXKAuthenticationViewController: Fix registration with email. --- .../MXKAuthenticationViewController.m | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/MatrixKit/Controllers/MXKAuthenticationViewController.m b/MatrixKit/Controllers/MXKAuthenticationViewController.m index 9b69bd114..0cf841c4b 100644 --- a/MatrixKit/Controllers/MXKAuthenticationViewController.m +++ b/MatrixKit/Controllers/MXKAuthenticationViewController.m @@ -844,32 +844,32 @@ - (void)registerWithParameters:(NSDictionary*)parameters { MXAuthenticationSession *authSession = [MXAuthenticationSession modelFromJSON:JSONResponse]; - if ([authSession.session isEqualToString:self.authInputsView.authSession.session]) + if (authSession.completed) { - if (authSession.completed) - { - [_authenticationActivityIndicator stopAnimating]; + [_authenticationActivityIndicator stopAnimating]; + + // Update session identifier in case of change + self.authInputsView.authSession.session = authSession.session; + + [self.authInputsView updateAuthSessionWithCompletedStages:authSession.completed didUpdateParameters:^(NSDictionary *parameters) { - [self.authInputsView updateAuthSessionWithCompletedStages:authSession.completed didUpdateParameters:^(NSDictionary *parameters) { + if (parameters) + { + NSLog(@"[MXKAuthenticationVC] Pursue registration"); - if (parameters) - { - NSLog(@"[MXKAuthenticationVC] Pursue registration"); - - [_authenticationActivityIndicator startAnimating]; - [self registerWithParameters:parameters]; - } - else - { - NSLog(@"[MXKAuthenticationVC] Failed to update parameters"); - - [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; - } + [_authenticationActivityIndicator startAnimating]; + [self registerWithParameters:parameters]; + } + else + { + NSLog(@"[MXKAuthenticationVC] Failed to update parameters"); - }]; + [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; + } - return; - } + }]; + + return; } [self onFailureDuringAuthRequest:[NSError errorWithDomain:MXKAuthErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]}]]; From 8087a76202234a9b67887886e86a6a5b7556190d Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 5 Apr 2016 08:45:37 +0200 Subject: [PATCH 25/30] New email binding: Fixed issue reported by Giom's review --- MatrixKit/Controllers/MXKAccountDetailsViewController.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MatrixKit/Controllers/MXKAccountDetailsViewController.m b/MatrixKit/Controllers/MXKAccountDetailsViewController.m index 8a2cf823f..ac9507426 100644 --- a/MatrixKit/Controllers/MXKAccountDetailsViewController.m +++ b/MatrixKit/Controllers/MXKAccountDetailsViewController.m @@ -660,7 +660,9 @@ - (void)showValidationEmailDialogWithMessage:(NSString*)message alert.cancelButtonIndex = [alert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"abort"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert){ - emailSubmitButton.enabled = NO; + [alertsArray removeObject:alert]; + + emailSubmitButton.enabled = YES; }]; From 0eb35b19602e1f25491612057ce6f391e5283d33 Mon Sep 17 00:00:00 2001 From: giomfo Date: Tue, 5 Apr 2016 18:36:56 +0200 Subject: [PATCH 26/30] MXKAuthenticationViewController: Improve homeserver URL handling. --- .../MXKAuthenticationViewController.h | 16 +++++ .../MXKAuthenticationViewController.m | 68 +++++++++++-------- .../MXKAuthInputsPasswordBasedView.m | 2 +- 3 files changed, 57 insertions(+), 29 deletions(-) diff --git a/MatrixKit/Controllers/MXKAuthenticationViewController.h b/MatrixKit/Controllers/MXKAuthenticationViewController.h index ef62d50a2..74d9c8bae 100644 --- a/MatrixKit/Controllers/MXKAuthenticationViewController.h +++ b/MatrixKit/Controllers/MXKAuthenticationViewController.h @@ -177,6 +177,22 @@ extern NSString *const MXKAuthErrorDomain; */ - (IBAction)onButtonPressed:(id)sender; +/** + Set the home server url and force a new authentication session. + The default home server url is used when the provided url is nil. + + @param homeServerUrl the home server url to use + */ +- (void)setHomeServerTextFieldText:(NSString *)homeServerUrl; + +/** + Set the identity server url. + The default identity server url is used when the provided url is nil. + + @param identityServerUrl the identity server url to use + */ +- (void)setIdentityServerTextFieldText:(NSString *)identityServerUrl; + /** Force dismiss keyboard */ diff --git a/MatrixKit/Controllers/MXKAuthenticationViewController.m b/MatrixKit/Controllers/MXKAuthenticationViewController.m index 0cf841c4b..53c62a8ff 100644 --- a/MatrixKit/Controllers/MXKAuthenticationViewController.m +++ b/MatrixKit/Controllers/MXKAuthenticationViewController.m @@ -412,10 +412,7 @@ - (void)setDefaultHomeServerUrl:(NSString *)defaultHomeServerUrl if (!_homeServerTextField.text.length) { - _homeServerTextField.text = _defaultHomeServerUrl; - - // Update UI - [self textFieldDidEndEditing:_homeServerTextField]; + [self setHomeServerTextFieldText:defaultHomeServerUrl]; } } @@ -425,10 +422,44 @@ - (void)setDefaultIdentityServerUrl:(NSString *)defaultIdentityServerUrl if (!_identityServerTextField.text.length) { + [self setIdentityServerTextFieldText:defaultIdentityServerUrl]; + } +} + +- (void)setHomeServerTextFieldText:(NSString *)homeServerUrl +{ + if (homeServerUrl.length) + { + _homeServerTextField.text = homeServerUrl; + } + else + { + // Force refresh with default value + _homeServerTextField.text = _defaultHomeServerUrl; + } + + [self updateRESTClient]; + + // Refresh UI + [self refreshAuthenticationSession]; +} + +- (void)setIdentityServerTextFieldText:(NSString *)identityServerUrl +{ + if (identityServerUrl.length) + { + _identityServerTextField.text = identityServerUrl; + } + else + { + // Force refresh with default value _identityServerTextField.text = _defaultIdentityServerUrl; - - // Update UI - [self textFieldDidEndEditing:_identityServerTextField]; + } + + // Update REST client + if (mxRestClient) + { + [mxRestClient setIdentityServer:_identityServerTextField.text]; } } @@ -1124,30 +1155,11 @@ - (void)textFieldDidEndEditing:(UITextField *)textField { if (textField == _homeServerTextField) { - if (!textField.text.length) - { - // Force refresh with default value - textField.text = _defaultHomeServerUrl; - } - - [self updateRESTClient]; - - // Refresh UI - [self refreshAuthenticationSession]; + [self setHomeServerTextFieldText:textField.text]; } else if (textField == _identityServerTextField) { - if (!textField.text.length) - { - // Force refresh with default value - textField.text = _defaultIdentityServerUrl; - } - - // Update REST client - if (mxRestClient) - { - [mxRestClient setIdentityServer:textField.text]; - } + [self setIdentityServerTextFieldText:textField.text]; } } diff --git a/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m b/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m index e139b9735..6ad699a3f 100644 --- a/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m +++ b/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.m @@ -88,7 +88,7 @@ - (NSString*)validateParameters return errorMsg; } -- (void)prepareParameters:(void (^)(NSDictionary *parameters))callback; +- (void)prepareParameters:(void (^)(NSDictionary *parameters))callback { if (callback) { From 43676bc8be484793673d029685ce3c31829c7c6a Mon Sep 17 00:00:00 2001 From: giomfo Date: Wed, 6 Apr 2016 18:26:35 +0200 Subject: [PATCH 27/30] Fix vector-ios #196. Include address book emails in search result. --- MatrixKit/Models/Contact/MXKContactManager.h | 13 +++-- MatrixKit/Models/Contact/MXKContactManager.m | 56 +++++++++++++++++++- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/MatrixKit/Models/Contact/MXKContactManager.h b/MatrixKit/Models/Contact/MXKContactManager.h index d040a3629..8227d4493 100644 --- a/MatrixKit/Models/Contact/MXKContactManager.h +++ b/MatrixKit/Models/Contact/MXKContactManager.h @@ -95,18 +95,25 @@ typedef NS_ENUM(NSInteger, MXKContactManagerMXRoomSource) { */ @property (nonatomic, readonly) NSArray *mxSessions; +/** + The current list of the contacts extracted from matrix data. + */ +@property (nonatomic, readonly) NSArray *matrixContacts; + /** The current list of the local contacts (nil by default until the contacts are loaded). */ @property (nonatomic, readonly) NSArray *localContacts; /** - The current list of the contacts extracted from matrix data (nil by default until the contacts are loaded). + The current list of contacts used to handle the email addresses retrieved from the local address book. + Each item of this array is a MXKContact instance with an email address as display name. */ -@property (nonatomic, readonly) NSArray *matrixContacts; +@property (nonatomic, readonly) NSArray *localEmailContacts; /** - No by default. Set YES to update matrix ids for all the local contacts in only one request when device contacts are loaded and an identity server is available. + No by default. Set YES to update matrix ids for all the local contacts in only one request + when device contacts are loaded and an identity server is available. */ @property (nonatomic) BOOL enableFullMatrixIdSyncOnLocalContactsDidLoad; diff --git a/MatrixKit/Models/Contact/MXKContactManager.m b/MatrixKit/Models/Contact/MXKContactManager.m index 48e7e2732..3630c38db 100644 --- a/MatrixKit/Models/Contact/MXKContactManager.m +++ b/MatrixKit/Models/Contact/MXKContactManager.m @@ -53,6 +53,7 @@ Listeners registered on matrix presence and membership events (one by matrix ses NSDate *lastSyncDate; // Local contacts by contact Id NSMutableDictionary* localContactByContactID; + NSMutableArray* localEmailContacts; // Matrix id linked to 3PID. NSMutableDictionary* matrixIDBy3PID; // Keep history of 3PID lookup requests @@ -280,6 +281,12 @@ - (NSArray*)mxSessions return [NSArray arrayWithArray:mxSessionArray]; } + +- (NSArray*)matrixContacts +{ + return [matrixContactByContactID allValues]; +} + - (NSArray*)localContacts { // Return nil if the loading step is in progress. @@ -291,9 +298,50 @@ - (NSArray*)localContacts return [localContactByContactID allValues]; } -- (NSArray*)matrixContacts +- (NSArray*)localEmailContacts { - return [matrixContactByContactID allValues]; + // Return nil if the loading step is in progress. + if (isLocalContactListLoading) + { + return nil; + } + + // Check whether the array must be prepared + if (!localEmailContacts) + { + // List all the known emails from the local contacts + NSArray *localContacts = self.localContacts; + NSMutableArray *emailAddresses = [NSMutableArray arrayWithCapacity:localContacts.count]; + + for (MXKContact* contact in localContacts) + { + NSArray *emails = contact.emailAddresses; + for (MXKEmail *email in emails) + { + if (email.emailAddress.length) + { + if ([emailAddresses indexOfObject:email.emailAddress] == NSNotFound) + { + [emailAddresses addObject:email.emailAddress]; + } + } + } + } + + if (emailAddresses.count) + { + // Create here the local email contacts array + localEmailContacts = [NSMutableArray arrayWithCapacity:localContacts.count]; + + for (NSString *emailAddress in emailAddresses) + { + MXKContact *contact = [[MXKContact alloc] initMatrixContactWithDisplayName:emailAddress andMatrixID:nil]; + [localEmailContacts addObject:contact]; + } + } + } + + return localEmailContacts; } - (void)setIdentityServer:(NSString *)identityServer @@ -476,6 +524,9 @@ - (void)loadLocalContacts // something has been modified in the local contact book if (contactBookUpdate) { + // Remove the local email contacts (This array will be prepared only if need) + localEmailContacts = nil; + [self cacheLocalContacts]; } @@ -679,6 +730,7 @@ - (void)reset isLocalContactListLoading = NO; localContactByContactID = nil; + localEmailContacts = nil; [self cacheLocalContacts]; matrixContactByContactID = nil; From db03a5ec11451343054821ab4c321530afdce11c Mon Sep 17 00:00:00 2001 From: giomfo Date: Thu, 7 Apr 2016 09:20:00 +0200 Subject: [PATCH 28/30] MXKAuthenticationViewController: ignore connection cancellation error --- MatrixKit/Controllers/MXKAuthenticationViewController.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MatrixKit/Controllers/MXKAuthenticationViewController.m b/MatrixKit/Controllers/MXKAuthenticationViewController.m index 53c62a8ff..079e8d813 100644 --- a/MatrixKit/Controllers/MXKAuthenticationViewController.m +++ b/MatrixKit/Controllers/MXKAuthenticationViewController.m @@ -1005,6 +1005,12 @@ - (void)onFailureDuringAuthRequest:(NSError *)error NSLog(@"[MXKAuthenticationVC] Auth request failed: %@", error); + // Ignore connection cancellation error + if (([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled)) + { + return; + } + // Translate the error code to a human message NSString *title = error.localizedFailureReason; if (!title) From dc5749bfbb0ab0bcfc523ce62cf3f3c40abdad28 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 7 Apr 2016 16:49:07 +0200 Subject: [PATCH 29/30] MXKAccountManager: Added accountKnowingRoomWithRoomIdOrAlias method --- MatrixKit/Models/Account/MXKAccountManager.h | 10 +++++++ MatrixKit/Models/Account/MXKAccountManager.m | 28 ++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/MatrixKit/Models/Account/MXKAccountManager.h b/MatrixKit/Models/Account/MXKAccountManager.h index 85adf2828..bf9ec04ad 100644 --- a/MatrixKit/Models/Account/MXKAccountManager.h +++ b/MatrixKit/Models/Account/MXKAccountManager.h @@ -115,4 +115,14 @@ extern NSString *const kMXKAccountManagerDidRemoveAccountNotification; */ - (MXKAccount*)accountForUserId:(NSString*)userId; +/** + Retrieve an account that knows the room with the passed id or alias. + + Note: The method is not accurate as it returns the first account that matches. + + @param roomIdOrAlias the room id or alias. + @return the user's account. Nil if no account matches. + */ +- (MXKAccount*)accountKnowingRoomWithRoomIdOrAlias:(NSString*)roomIdOrAlias; + @end diff --git a/MatrixKit/Models/Account/MXKAccountManager.m b/MatrixKit/Models/Account/MXKAccountManager.m index 062ac3302..431d5667f 100644 --- a/MatrixKit/Models/Account/MXKAccountManager.m +++ b/MatrixKit/Models/Account/MXKAccountManager.m @@ -158,6 +158,34 @@ - (MXKAccount *)accountForUserId:(NSString *)userId return nil; } +- (MXKAccount *)accountKnowingRoomWithRoomIdOrAlias:(NSString *)roomIdOrAlias +{ + MXKAccount *theAccount = nil; + + NSArray *mxAccounts = self.activeAccounts; + + for (MXKAccount *account in mxAccounts) + { + if ([roomIdOrAlias hasPrefix:@"#"]) + { + if ([account.mxSession roomWithAlias:roomIdOrAlias]) + { + theAccount = account; + break; + } + } + else + { + if ([account.mxSession roomWithRoomId:roomIdOrAlias]) + { + theAccount = account; + break; + } + } + } + return theAccount; +} + #pragma mark - - (void)setStoreClass:(Class)storeClass From 7acd9e5b4dcdf5bcaa8385a8bf4e665f63cf81d7 Mon Sep 17 00:00:00 2001 From: giomfo Date: Fri, 8 Apr 2016 10:29:42 +0200 Subject: [PATCH 30/30] Prepare matrix-ios-kit release v0.3.5 --- CHANGES.rst | 19 +++++++++++++++++++ MatrixKit.podspec | 6 +++--- MatrixKit/Utils/MXKConstants.m | 2 +- Podfile | 4 ++-- Podfile.lock | 16 +++------------- 5 files changed, 28 insertions(+), 19 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index fb369e2e6..27d53ac64 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,22 @@ +Changes in MatrixKit in 0.3.5 (2016-04-08) +=============================================== + +Improvements: + * MXKAccountManager: API change - [openSessionForActiveAccounts] is replaced by [prepareSessionForActiveAccounts]. This new method checks for each enabled account if a matrix session is already opened. It opens a matrix session for each enabled account which doesn't have a session. + * MXK3PID: support new email binding mechanism. + * MXKAuthenticationViewController, MXKAuthInputsView: Support registration based on MXAuthenticationSession class. + * MXKAuthenticationRecaptchaWebView: Display a reCAPTCHA widget into a webview. + * MXKAccountDetailsViewController: Handle the linked emails. + * MXKAccount: Store (permanently) 3PIDs. + * MXKRecentsDataSource: Remove room notifications and room tags handling (These operations are handled by inherited classes). + * MXKContactManager: List email addresses from the local address book (see 'localEmailContacts'). + * MXKAccountManager: Added accountKnowingRoomWithRoomIdOrAlias method. + +Bug fixes: + * Search: 'no result' label is persistent #75. + * MXKAccount: the push gateway URL must be configurable #76. + * Multiple invitations on Start Chat action. + Changes in MatrixKit in 0.3.4 (2016-03-17) =============================================== diff --git a/MatrixKit.podspec b/MatrixKit.podspec index 407984ec4..41295484a 100644 --- a/MatrixKit.podspec +++ b/MatrixKit.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "MatrixKit" - s.version = "0.3.4" + s.version = "0.3.5" s.summary = "The Matrix reusable UI library for iOS based on MatrixSDK." s.description = <<-DESC @@ -17,13 +17,13 @@ Pod::Spec.new do |s| s.platform = :ios, "7.0" - s.source = { :git => "https://github.com/matrix-org/matrix-ios-kit.git", :tag => "v0.3.4" } + s.source = { :git => "https://github.com/matrix-org/matrix-ios-kit.git", :tag => "v0.3.5" } s.source_files = "MatrixKit", "MatrixKit/**/*.{h,m}" s.resources = "MatrixKit/**/*.{xib}", "MatrixKit/Assets/MatrixKitAssets.bundle" s.requires_arc = true - s.dependency 'MatrixSDK', '~> 0.6.4' + s.dependency 'MatrixSDK', '~> 0.6.5' s.dependency 'HPGrowingTextView', '~> 1.1' s.dependency 'libPhoneNumber-iOS', '~> 0.8.7' diff --git a/MatrixKit/Utils/MXKConstants.m b/MatrixKit/Utils/MXKConstants.m index 2dba3d5cc..dfbb653a5 100644 --- a/MatrixKit/Utils/MXKConstants.m +++ b/MatrixKit/Utils/MXKConstants.m @@ -16,6 +16,6 @@ #import "MXKConstants.h" -NSString *const MatrixKitVersion = @"0.3.4"; +NSString *const MatrixKitVersion = @"0.3.5"; NSString *const kMXKErrorNotification = @"kMXKErrorNotification"; \ No newline at end of file diff --git a/Podfile b/Podfile index a02fc1d82..bb7eb47d8 100644 --- a/Podfile +++ b/Podfile @@ -8,13 +8,13 @@ target "MatrixKitSample" do # Different flavours of pods to Matrix SDK # The tagged version on which this version of MatrixKit has been built -#pod 'MatrixSDK', '~> 0.6.4' +pod 'MatrixSDK', '~> 0.6.5' # The lastest release available on the CocoaPods repository #pod 'MatrixSDK' # The develop branch version -pod 'MatrixSDK', :git => 'https://github.com/matrix-org/matrix-ios-sdk.git', :branch => 'develop' +#pod 'MatrixSDK', :git => 'https://github.com/matrix-org/matrix-ios-sdk.git', :branch => 'develop' # The one used for developping both MatrixSDK and MatrixKit # Note that MatrixSDK must be cloned into a folder called matrix-ios-sdk next to the MatrixKit folder diff --git a/Podfile.lock b/Podfile.lock index 36aa08eff..9d840324b 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -25,24 +25,14 @@ PODS: - JSQSystemSoundPlayer (~> 2.0.1) - JSQSystemSoundPlayer (2.0.1) - libPhoneNumber-iOS (0.8.11) - - MatrixSDK (0.6.4): + - MatrixSDK (0.6.5): - AFNetworking (~> 2.6.0) DEPENDENCIES: - HPGrowingTextView (~> 1.1) - JSQMessagesViewController (~> 7.2.0) - libPhoneNumber-iOS (~> 0.8.7) - - MatrixSDK (from `https://github.com/matrix-org/matrix-ios-sdk.git`, branch `develop`) - -EXTERNAL SOURCES: - MatrixSDK: - :branch: develop - :git: https://github.com/matrix-org/matrix-ios-sdk.git - -CHECKOUT OPTIONS: - MatrixSDK: - :commit: c22c99d3e3a30fc9e3b1016a9990b9dcba4a8919 - :git: https://github.com/matrix-org/matrix-ios-sdk.git + - MatrixSDK (~> 0.6.5) SPEC CHECKSUMS: AFNetworking: cb8d14a848e831097108418f5d49217339d4eb60 @@ -50,6 +40,6 @@ SPEC CHECKSUMS: JSQMessagesViewController: 73cab48aa92fc2d512f3b6724f3425cc47a19eb5 JSQSystemSoundPlayer: c5850e77a4363ffd374cd851154b9af93264ed8d libPhoneNumber-iOS: ded33fab2c51ee847979556aa504c9e70f32d703 - MatrixSDK: 09985a664b3b158129421eeb096b17147a2f1bf9 + MatrixSDK: 598925faff319441724db8a6af2a9cfc21d167e5 COCOAPODS: 0.39.0