From 9636b0e839fa2d2629695a980b1b1e0b179ba6a2 Mon Sep 17 00:00:00 2001 From: Vitalii404 Date: Mon, 15 Jan 2018 20:05:49 +0200 Subject: [PATCH 01/10] added handler for facebook invalidated session; switched server to 2-minutes session lifetime(stage1); --- .../QBSettings/QBSettings+Qmunicate.h | 2 +- Q-municate/Classes/QMFacebook/QMFacebook.m | 87 +++++++++++-------- Q-municate/Classes/QMTasks/QMTasks.m | 4 +- .../QMDialogsViewController.m | 4 + .../Common classes/QMShareTasks.m | 2 +- 5 files changed, 61 insertions(+), 38 deletions(-) diff --git a/Q-municate/Categories/QBSettings/QBSettings+Qmunicate.h b/Q-municate/Categories/QBSettings/QBSettings+Qmunicate.h index de94b8325..86a208139 100644 --- a/Q-municate/Categories/QBSettings/QBSettings+Qmunicate.h +++ b/Q-municate/Categories/QBSettings/QBSettings+Qmunicate.h @@ -15,7 +15,7 @@ typedef NS_ENUM(NSUInteger, QMApplicationZone) { QMApplicationZoneQA, }; -static const QMApplicationZone QMCurrentApplicationZone = QMApplicationZoneDevelopment; +static const QMApplicationZone QMCurrentApplicationZone = QMApplicationZoneQA; @interface QBSettings (Qmunicate) diff --git a/Q-municate/Classes/QMFacebook/QMFacebook.m b/Q-municate/Classes/QMFacebook/QMFacebook.m index 9e9469300..96cb33983 100644 --- a/Q-municate/Classes/QMFacebook/QMFacebook.m +++ b/Q-municate/Classes/QMFacebook/QMFacebook.m @@ -18,46 +18,63 @@ static NSString * const kQMAppName = @"Q-municate"; static NSString * const kQMDataKey = @"data"; -static NSString * const kFBGraphGetPictureFormat = @"https://graph.facebook.com/%@/picture?height=100&width=100&access_token=%@"; +static NSString * const kFBGraphGetPictureFormat = +@"https://graph.facebook.com/%@/picture?height=100&width=100&access_token=%@"; @implementation QMFacebook + (BFTask *)connect { - BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; - FBSDKAccessToken *session = [FBSDKAccessToken currentAccessToken]; if (!session) { - UINavigationController *navigationController = (UINavigationController *)[[UIApplication sharedApplication].windows.firstObject rootViewController]; + UINavigationController *navigationController = + (id)[[UIApplication sharedApplication].windows.firstObject rootViewController]; + + NSArray *readPermissions = + @[@"email", @"public_profile", @"user_friends"]; FBSDKLoginManager *loginManager = [[FBSDKLoginManager alloc] init]; - [loginManager logInWithReadPermissions:@[@"email", @"public_profile", @"user_friends"] - fromViewController:navigationController - handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) { - - if (error) { - - [source setError:error]; - } - else if (result.isCancelled) { - - [source cancel]; - - } - else { - - [source setResult:result.token.tokenString]; - } - }]; + + return make_task(^(BFTaskCompletionSource * _Nonnull source) { + + [loginManager logInWithReadPermissions:readPermissions + fromViewController:navigationController + handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) + { + + if (error) { + [source setError:error]; + } + else if (result.isCancelled) { + [source cancel]; + } + else { + [source setResult:result.token.tokenString]; + } + }]; + + }); } else { - [source setResult:session.tokenString]; + /* + Handling an Invalidated Session. + + We cannot know if the cached Facebook session is valid until we attempt to make a request to the API. + A session can become invalidated if a user changes their password or revokes the application's privileges. + When this happens, the user needs to be logged out. We can identify an invalid session error within a FBSDKGraphRequest completion + handler. + + If the request fails, we can check if it was due to an invalid session by: + if ([error.userInfo[@"error"][@"type"] isEqualToString: @"OAuthException"]) + */ + + return [[self loadMe] continueWithSuccessBlock:^id _Nullable(BFTask * _Nonnull __unused t) { + return [BFTask taskWithResult:session.tokenString]; + }]; } - - return source.task; } + (NSURL *)userImageUrlWithUserID:(NSString *)userID { @@ -70,16 +87,18 @@ + (NSURL *)userImageUrlWithUserID:(NSString *)userID { + (BFTask *)loadMe { - BFTaskCompletionSource* source = [BFTaskCompletionSource taskCompletionSource]; - - FBSDKGraphRequest *friendsRequest = [[FBSDKGraphRequest alloc] initWithGraphPath:@"me" parameters:nil]; - - [friendsRequest startWithCompletionHandler:^(FBSDKGraphRequestConnection *__unused connection, id result, NSError *error) { + return make_task(^(BFTaskCompletionSource * _Nonnull source) { - error != nil ? [source setError:error] : [source setResult:result]; - }]; - - return source.task; + FBSDKGraphRequest *myInfoRequest = + [[FBSDKGraphRequest alloc] initWithGraphPath:@"me" parameters:nil]; + + [myInfoRequest startWithCompletionHandler:^(FBSDKGraphRequestConnection *__unused connection, + id result, + NSError *error) + { + error ? [source setError:error] : [source setResult:result]; + }]; + }); } + (void)logout { diff --git a/Q-municate/Classes/QMTasks/QMTasks.m b/Q-municate/Classes/QMTasks/QMTasks.m index de9505510..6d518b20d 100644 --- a/Q-municate/Classes/QMTasks/QMTasks.m +++ b/Q-municate/Classes/QMTasks/QMTasks.m @@ -116,8 +116,8 @@ + (BFTask *)taskAutoLogin { } else if (type == QMAccountTypeFacebook) { - return [[QMFacebook connect] continueWithBlock:^id(BFTask *task) { - return task.result ? [core.authService loginWithFacebookSessionToken:task.result] : nil; + return [[QMFacebook connect] continueWithSuccessBlock:^id(BFTask *task) { + return [core.authService loginWithFacebookSessionToken:task.result]; }]; } else if (type == QMAccountTypePhone) { diff --git a/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m b/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m index 745e6a7b5..2f44e13ba 100644 --- a/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m +++ b/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m @@ -24,6 +24,9 @@ #import "QMNavigationBar.h" #import +static BOOL isFacebookError(NSError *error) { + return [error.userInfo[@"error"][@"type"] isEqualToString:@"OAuthException"]; +} static const NSInteger kQMNotAuthorizedInRest = -1000; static const NSInteger kQMUnauthorizedErrorCode = -1011; @@ -171,6 +174,7 @@ - (void)performAutoLoginAndFetchData { NSInteger errorCode = task.error.code; if (errorCode == kQMNotAuthorizedInRest || errorCode == kQMUnauthorizedErrorCode + || isFacebookError(task.error) || (errorCode == kBFMultipleErrorsError && ([task.error.userInfo[BFTaskMultipleErrorsUserInfoKey][0] code] == kQMUnauthorizedErrorCode || [task.error.userInfo[BFTaskMultipleErrorsUserInfoKey][1] code] == kQMUnauthorizedErrorCode))) { diff --git a/QMShareExtension/Common classes/QMShareTasks.m b/QMShareExtension/Common classes/QMShareTasks.m index b5c6df102..83e77b6a9 100644 --- a/QMShareExtension/Common classes/QMShareTasks.m +++ b/QMShareExtension/Common classes/QMShareTasks.m @@ -24,7 +24,7 @@ @implementation QMItemProviderResult - (NSString *)description { NSMutableString *result = [NSMutableString stringWithString:[super description]]; - [result appendFormat:@"Text: %@\n", _text]; + [result appendFormat:@"Text: %@/n", _text]; [result appendFormat:@"Attachment: %@",_attachment]; return result.copy; From 9a3929733ecd27731b0aceec387cf6bbbf28dcb1 Mon Sep 17 00:00:00 2001 From: Vitalii404 Date: Tue, 16 Jan 2018 03:39:42 +0200 Subject: [PATCH 02/10] reworked login workflow --- Q-municate/QMAppDelegate.m | 2 - .../QMDialogsViewController.m | 41 +++++++++---------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/Q-municate/QMAppDelegate.m b/Q-municate/QMAppDelegate.m index 4b37d986b..76c76a502 100644 --- a/Q-municate/QMAppDelegate.m +++ b/Q-municate/QMAppDelegate.m @@ -117,8 +117,6 @@ - (void)applicationWillEnterForeground:(UIApplication *)__unused application { if (QBChat.instance.manualInitialPresence) { QBChat.instance.manualInitialPresence = NO; } - // connect to chat now - [QMCore.instance login]; } - (void)applicationDidBecomeActive:(UIApplication *)__unused application { diff --git a/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m b/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m index 2f44e13ba..1535cc163 100644 --- a/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m +++ b/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m @@ -43,8 +43,7 @@ @interface QMDialogsViewController () // Data sources @property (strong, nonatomic) QMDialogsDataSource *dialogsDataSource; @property (strong, nonatomic) QMDialogsSearchDataSource *dialogsSearchDataSource; - -@property (weak, nonatomic) BFTask *addUserTask; +@property (weak, nonatomic) BFTask *loginTask; @property (strong, nonatomic) id observerWillEnterForeground; @@ -93,7 +92,7 @@ - (void)viewDidLoad { queue:nil usingBlock:^(NSNotification * _Nonnull __unused note) { - @strongify(self); + if ([QBChat instance].isConnected) { // if chat was connected (e.g. we are in call) in background // we skip requests, so perform them now as app is active now @@ -101,9 +100,7 @@ - (void)viewDidLoad { [QMTasks taskUpdateContacts]; } else { - [(QMNavigationController *)self.navigationController showNotificationWithType:QMNotificationPanelTypeLoading - message:NSLocalizedString(@"QM_STR_CONNECTING", nil) - duration:0]; + [self performAutoLoginAndFetchData]; } }]; @@ -112,7 +109,7 @@ - (void)viewDidLoad { NSDate *lastFetchDate = QMCore.instance.currentProfile.lastDialogsFetchingDate; - @strongify(self); + @strongify(self); [[QMCore.instance.chatService syncLaterDialogsWithCacheFromDate:lastFetchDate] continueWithBlock:^id _Nullable(BFTask *> * _Nonnull t) { if (t.result.count > 0) { @@ -153,11 +150,6 @@ - (void)viewWillAppear:(BOOL)animated { - (void)performAutoLoginAndFetchData { - QMNavigationController *navigationController = (id)self.navigationController; - [navigationController showNotificationWithType:QMNotificationPanelTypeLoading - message:NSLocalizedString(@"QM_STR_CONNECTING", nil) - duration:0]; - if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground && !QBChat.instance.manualInitialPresence) { // connecting to chat with manual initial presence if in the background @@ -165,8 +157,14 @@ - (void)performAutoLoginAndFetchData { QBChat.instance.manualInitialPresence = YES; } - [[[QMCore.instance login] continueWithBlock:^id(BFTask *task) { + if (self.loginTask) { + return; + } + + + self.loginTask = [[QMCore.instance login] continueWithBlock:^id(BFTask *task) { + //Perform logout task in case user is not athorized or facebook session is invalidated if (task.isFaulted) { [navigationController dismissNotificationPanel]; @@ -179,10 +177,16 @@ - (void)performAutoLoginAndFetchData { && ([task.error.userInfo[BFTaskMultipleErrorsUserInfoKey][0] code] == kQMUnauthorizedErrorCode || [task.error.userInfo[BFTaskMultipleErrorsUserInfoKey][1] code] == kQMUnauthorizedErrorCode))) { - return [QMCore.instance logout]; + return [[QMCore.instance logout] continueWithBlock:^id _Nullable(BFTask * _Nonnull __unused t) { + [self performSegueWithIdentifier:kQMSceneSegueAuth sender:nil]; + return nil; + }]; } } + [QMTasks taskFetchAllData]; + [QMTasks taskUpdateContacts]; + if (QMCore.instance.pushNotificationManager.pushNotification != nil) { [QMCore.instance.pushNotificationManager handlePushNotificationWithDelegate:self]; } @@ -191,15 +195,8 @@ - (void)performAutoLoginAndFetchData { [QMCore.instance.pushNotificationManager registerAndSubscribeForPushNotifications]; } - return [BFTask cancelledTask]; - - }] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) { - - if (!task.isCancelled) { - [self performSegueWithIdentifier:kQMSceneSegueAuth sender:nil]; - } - return nil; + }]; } From 19a4b84bc838cf7d4cf855199dbc3756a96f807a Mon Sep 17 00:00:00 2001 From: Vitalii404 Date: Tue, 16 Jan 2018 03:40:33 +0200 Subject: [PATCH 03/10] changed notification panel with simple activity controller --- .../QMDialogsViewController.m | 46 +++++++++++++++---- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m b/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m index 1535cc163..6608a2195 100644 --- a/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m +++ b/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m @@ -53,6 +53,33 @@ @implementation QMDialogsViewController //MARK: - Life cycle +- (void)showLoadingWithStatus:(NSString *)status { + + if (status) { + self.navigationItem.title = status; + } + + UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:activityIndicator]; + [activityIndicator startAnimating]; + self.navigationItem.leftBarButtonItem = item; +} + +- (void)dismissLoadingWithResultStatus:(NSString *)resultStatus { + + self.navigationItem.leftBarButtonItem = nil; + if (resultStatus) { + self.navigationItem.title = resultStatus; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kQMDefaultNotificationDismissTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + self.navigationItem.title = @"Chats"; + }); + } + else { + self.navigationItem.title = @"Chats"; + } +} + + - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:_observerWillEnterForeground]; @@ -74,6 +101,11 @@ - (void)viewDidLoad { // registering nibs for current VC and search results VC [self registerNibs]; + CATransition *fadeTextAnimation = [CATransition animation]; + fadeTextAnimation.duration = 1.0; + fadeTextAnimation.type = kCATransitionFade; + [self.navigationController.navigationBar.layer addAnimation: fadeTextAnimation + forKey:@"fadeText"]; [self performAutoLoginAndFetchData]; // adding refresh control task if (self.refreshControl) { @@ -161,13 +193,14 @@ - (void)performAutoLoginAndFetchData { return; } + [self showLoadingWithStatus:NSLocalizedString(@"QM_STR_CONNECTING", nil)]; self.loginTask = [[QMCore.instance login] continueWithBlock:^id(BFTask *task) { //Perform logout task in case user is not athorized or facebook session is invalidated if (task.isFaulted) { - [navigationController dismissNotificationPanel]; + [self dismissLoadingWithResultStatus:nil]; NSInteger errorCode = task.error.code; if (errorCode == kQMNotAuthorizedInRest @@ -413,11 +446,7 @@ - (void)chatServiceChatHasStartedConnecting:(QMChatService *)__unused chatServic } - (void)chatServiceChatDidConnect:(QMChatService *)__unused chatService { - - [(QMNavigationController *)self.navigationController - showNotificationWithType:QMNotificationPanelTypeSuccess - message:NSLocalizedString(@"QM_STR_CHAT_CONNECTED", nil) - duration:kQMDefaultNotificationDismissTime]; + [self dismissLoadingWithResultStatus:nil]; } - (void)chatServiceChatDidReconnect:(QMChatService *)__unused chatService { @@ -426,10 +455,7 @@ - (void)chatServiceChatDidReconnect:(QMChatService *)__unused chatService { [QMTasks taskFetchAllData]; } - [(QMNavigationController *)self.navigationController - showNotificationWithType:QMNotificationPanelTypeSuccess - message:NSLocalizedString(@"QM_STR_CHAT_RECONNECTED", nil) - duration:kQMDefaultNotificationDismissTime]; + [self dismissLoadingWithResultStatus:nil]; } - (void)contactListService:(QMContactListService *)__unused contactListService From b467afd48a5684012c4470b3f3960d535ad0333a Mon Sep 17 00:00:00 2001 From: Vitalii404 Date: Tue, 16 Jan 2018 04:10:42 +0200 Subject: [PATCH 04/10] added titleview with loading progress --- .../QMDialogsViewController.m | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m b/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m index 6608a2195..7793d94c8 100644 --- a/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m +++ b/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m @@ -54,28 +54,46 @@ @implementation QMDialogsViewController //MARK: - Life cycle - (void)showLoadingWithStatus:(NSString *)status { + + UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + activityIndicatorView.frame = CGRectMake(0, 0, 22, 22); + activityIndicatorView.color = [UIColor blackColor]; + [activityIndicatorView startAnimating]; - if (status) { - self.navigationItem.title = status; - } + UILabel *titleLabel = [UILabel new]; + titleLabel.text = status; + titleLabel.font = [UIFont boldSystemFontOfSize:18]; + #warning For the love of God fix this + titleLabel.tag = 77; + CGSize fittingSize = [titleLabel sizeThatFits:CGSizeMake(200.0f, activityIndicatorView.frame.size.height)]; + titleLabel.frame = CGRectMake(activityIndicatorView.frame.origin.x + activityIndicatorView.frame.size.width + 8, + activityIndicatorView.frame.origin.y, + fittingSize.width, + fittingSize.height); + + UIView *titleView = [[UIView alloc] initWithFrame:CGRectMake(-(activityIndicatorView.frame.size.width + 8 + titleLabel.frame.size.width)/2, + -(activityIndicatorView.frame.size.height)/2, + activityIndicatorView.frame.size.width + 8 + titleLabel.frame.size.width, + activityIndicatorView.frame.size.height)]; + [titleView addSubview:activityIndicatorView]; + [titleView addSubview:titleLabel]; - UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; - UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:activityIndicator]; - [activityIndicator startAnimating]; - self.navigationItem.leftBarButtonItem = item; + self.navigationItem.titleView = titleView; } - (void)dismissLoadingWithResultStatus:(NSString *)resultStatus { self.navigationItem.leftBarButtonItem = nil; if (resultStatus) { - self.navigationItem.title = resultStatus; + #warning For the love of God fix this + UILabel *label = [self.navigationItem.titleView viewWithTag:77]; + label.text = resultStatus; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kQMDefaultNotificationDismissTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - self.navigationItem.title = @"Chats"; + self.navigationItem.titleView = nil; }); } else { - self.navigationItem.title = @"Chats"; + self.navigationItem.titleView = nil; } } From ba53cd11f516f41607c533b85f751f59ff505c5f Mon Sep 17 00:00:00 2001 From: Vitalii404 Date: Tue, 16 Jan 2018 23:59:56 +0200 Subject: [PATCH 05/10] no needs to perform login in background application state if internet connection appears --- Q-municate/Classes/Core/QMCore/QMCore.m | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Q-municate/Classes/Core/QMCore/QMCore.m b/Q-municate/Classes/Core/QMCore/QMCore.m index 0106d76a9..f5ced3a8c 100644 --- a/Q-municate/Classes/Core/QMCore/QMCore.m +++ b/Q-municate/Classes/Core/QMCore/QMCore.m @@ -98,6 +98,10 @@ - (void)configureReachability { @weakify(self); [_internetConnection setReachableBlock:^(Reachability __unused *reachability) { + if (UIApplication.sharedApplication.applicationState == UIApplicationStateBackground) { + //No needs to perform login if application is in background state + return; + } @strongify(self); dispatch_async(dispatch_get_main_queue(), ^{ // reachability block could possibly be called in background thread @@ -208,7 +212,8 @@ - (BFTask *)login { return [[QMTasks taskAutoLogin] continueWithSuccessBlock:^id(BFTask *task) { - return [self.chatService connectWithUserID:task.result.ID password:task.result.password]; + return [self.chatService connectWithUserID:task.result.ID + password:task.result.password]; }]; } From 50e52d87f9fd7c82b4342b59cabdf6497c67a150 Mon Sep 17 00:00:00 2001 From: Vitalii404 Date: Wed, 17 Jan 2018 00:01:00 +0200 Subject: [PATCH 06/10] disabled error recovering for facebook's "me" request --- Q-municate/Classes/QMFacebook/QMFacebook.m | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Q-municate/Classes/QMFacebook/QMFacebook.m b/Q-municate/Classes/QMFacebook/QMFacebook.m index 96cb33983..dfee229a4 100644 --- a/Q-municate/Classes/QMFacebook/QMFacebook.m +++ b/Q-municate/Classes/QMFacebook/QMFacebook.m @@ -21,6 +21,7 @@ static NSString * const kFBGraphGetPictureFormat = @"https://graph.facebook.com/%@/picture?height=100&width=100&access_token=%@"; + @implementation QMFacebook + (BFTask *)connect { @@ -28,7 +29,6 @@ + (BFTask *)connect { FBSDKAccessToken *session = [FBSDKAccessToken currentAccessToken]; if (!session) { - UINavigationController *navigationController = (id)[[UIApplication sharedApplication].windows.firstObject rootViewController]; @@ -43,7 +43,6 @@ + (BFTask *)connect { fromViewController:navigationController handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) { - if (error) { [source setError:error]; } @@ -70,7 +69,6 @@ + (BFTask *)connect { If the request fails, we can check if it was due to an invalid session by: if ([error.userInfo[@"error"][@"type"] isEqualToString: @"OAuthException"]) */ - return [[self loadMe] continueWithSuccessBlock:^id _Nullable(BFTask * _Nonnull __unused t) { return [BFTask taskWithResult:session.tokenString]; }]; @@ -91,7 +89,7 @@ + (BFTask *)loadMe { FBSDKGraphRequest *myInfoRequest = [[FBSDKGraphRequest alloc] initWithGraphPath:@"me" parameters:nil]; - + [myInfoRequest setGraphErrorRecoveryDisabled:YES]; [myInfoRequest startWithCompletionHandler:^(FBSDKGraphRequestConnection *__unused connection, id result, NSError *error) From 4e71987fa06619496907830a5854a55c42b112ce Mon Sep 17 00:00:00 2001 From: Vitalii404 Date: Wed, 17 Jan 2018 13:50:17 +0200 Subject: [PATCH 07/10] added error handling in case of facebook' invalid session --- Q-municate/Classes/QMTasks/QMTasks.m | 48 +++++++++++++++++++--------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/Q-municate/Classes/QMTasks/QMTasks.m b/Q-municate/Classes/QMTasks/QMTasks.m index 6d518b20d..6c6778fdf 100644 --- a/Q-municate/Classes/QMTasks/QMTasks.m +++ b/Q-municate/Classes/QMTasks/QMTasks.m @@ -116,8 +116,19 @@ + (BFTask *)taskAutoLogin { } else if (type == QMAccountTypeFacebook) { - return [[QMFacebook connect] continueWithSuccessBlock:^id(BFTask *task) { - return [core.authService loginWithFacebookSessionToken:task.result]; + return [[QMFacebook connect] continueWithBlock:^id(BFTask *task) { + + NSError *error = task.error; + if (error) { + if (isFacebookSessionError(error)) { + NSError *notLoggedError = [QMErrorsFactory errorNotLoggedInREST]; + return [BFTask taskWithError:notLoggedError]; + } + else { + return task; + } + } + return [core.authService loginWithFacebookSessionToken:task.result]; }]; } else if (type == QMAccountTypePhone) { @@ -168,10 +179,10 @@ + (BFTask *)taskFetchAllData { [self sliceArray:dialogsUsersIDs.allObjects limit:kQMUsersPageLimit enumerate:^(NSArray *slice, NSRange __unused range) - { - BFTask *> *task = [core.usersService getUsersWithIDs:slice]; - [usersLoadingTasks addObject:task]; - }]; + { + BFTask *> *task = [core.usersService getUsersWithIDs:slice]; + [usersLoadingTasks addObject:task]; + }]; } }; @@ -234,15 +245,15 @@ + (BFTask *)taskUpdateContacts { [self sliceArray:contactsIDs limit:kQMUsersPageLimit enumerate:^(NSArray *slice, NSRange range) - { - QBGeneralResponsePage *page = - [QBGeneralResponsePage responsePageWithCurrentPage:1 - perPage:range.length]; - BFTask *task = - [core.usersService searchUsersWithExtendedRequest:filterForUsersFetch(slice, dateFilter) - page:page]; - [tasks addObject:task]; - }]; + { + QBGeneralResponsePage *page = + [QBGeneralResponsePage responsePageWithCurrentPage:1 + perPage:range.length]; + BFTask *task = + [core.usersService searchUsersWithExtendedRequest:filterForUsersFetch(slice, dateFilter) + page:page]; + [tasks addObject:task]; + }]; BFTask *task = [[BFTask taskForCompletionOfAllTasks:[tasks copy]] continueWithSuccessBlock:^id(BFTask * __unused t) { core.currentProfile.lastUserFetchDate = [NSDate date]; @@ -287,4 +298,11 @@ + (void)sliceArray:(NSArray *)array return filters; } +static BOOL isFacebookSessionError(NSError *error) { + if (!error) { + return NO; + } + return [error.userInfo[@"error"][@"type"] isEqualToString:@"OAuthException"]; +} + @end From 4388a505a90c44bf00e2c7c6b5cbe4708ddbbc7f Mon Sep 17 00:00:00 2001 From: Vitalii404 Date: Wed, 17 Jan 2018 14:29:42 +0200 Subject: [PATCH 08/10] check application state on the main thread --- Q-municate/Classes/Core/QMCore/QMCore.m | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Q-municate/Classes/Core/QMCore/QMCore.m b/Q-municate/Classes/Core/QMCore/QMCore.m index f5ced3a8c..da5f4ea19 100644 --- a/Q-municate/Classes/Core/QMCore/QMCore.m +++ b/Q-municate/Classes/Core/QMCore/QMCore.m @@ -98,12 +98,13 @@ - (void)configureReachability { @weakify(self); [_internetConnection setReachableBlock:^(Reachability __unused *reachability) { - if (UIApplication.sharedApplication.applicationState == UIApplicationStateBackground) { - //No needs to perform login if application is in background state - return; - } - @strongify(self); dispatch_async(dispatch_get_main_queue(), ^{ + + if (UIApplication.sharedApplication.applicationState == UIApplicationStateBackground) { + //No needs to perform login if application is in background state + return; + } + @strongify(self); // reachability block could possibly be called in background thread [self login]; }); From 7328b74cf2faf7eebc60c4c8d122ffec7aa47c8e Mon Sep 17 00:00:00 2001 From: Vitalii404 Date: Wed, 17 Jan 2018 16:04:45 +0200 Subject: [PATCH 09/10] revert changes for dialogsVC --- .../QMDialogsViewController.m | 96 +++++-------------- 1 file changed, 22 insertions(+), 74 deletions(-) diff --git a/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m b/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m index 163c1a751..fb2fb88b5 100644 --- a/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m +++ b/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m @@ -24,9 +24,6 @@ #import "QMNavigationBar.h" #import -static BOOL isFacebookError(NSError *error) { - return [error.userInfo[@"error"][@"type"] isEqualToString:@"OAuthException"]; -} static const NSInteger kQMNotAuthorizedInRest = -1000; static const NSInteger kQMUnauthorizedErrorCode = -1011; @@ -43,7 +40,6 @@ @interface QMDialogsViewController () // Data sources @property (strong, nonatomic) QMDialogsDataSource *dialogsDataSource; @property (strong, nonatomic) QMDialogsSearchDataSource *dialogsSearchDataSource; -@property (weak, nonatomic) BFTask *loginTask; @property (strong, nonatomic) id observerWillEnterForeground; @@ -53,51 +49,6 @@ @implementation QMDialogsViewController //MARK: - Life cycle -- (void)showLoadingWithStatus:(NSString *)status { - - UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; - activityIndicatorView.frame = CGRectMake(0, 0, 22, 22); - activityIndicatorView.color = [UIColor blackColor]; - [activityIndicatorView startAnimating]; - - UILabel *titleLabel = [UILabel new]; - titleLabel.text = status; - titleLabel.font = [UIFont boldSystemFontOfSize:18]; - #warning For the love of God fix this - titleLabel.tag = 77; - CGSize fittingSize = [titleLabel sizeThatFits:CGSizeMake(200.0f, activityIndicatorView.frame.size.height)]; - titleLabel.frame = CGRectMake(activityIndicatorView.frame.origin.x + activityIndicatorView.frame.size.width + 8, - activityIndicatorView.frame.origin.y, - fittingSize.width, - fittingSize.height); - - UIView *titleView = [[UIView alloc] initWithFrame:CGRectMake(-(activityIndicatorView.frame.size.width + 8 + titleLabel.frame.size.width)/2, - -(activityIndicatorView.frame.size.height)/2, - activityIndicatorView.frame.size.width + 8 + titleLabel.frame.size.width, - activityIndicatorView.frame.size.height)]; - [titleView addSubview:activityIndicatorView]; - [titleView addSubview:titleLabel]; - - self.navigationItem.titleView = titleView; -} - -- (void)dismissLoadingWithResultStatus:(NSString *)resultStatus { - - self.navigationItem.leftBarButtonItem = nil; - if (resultStatus) { - #warning For the love of God fix this - UILabel *label = [self.navigationItem.titleView viewWithTag:77]; - label.text = resultStatus; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kQMDefaultNotificationDismissTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - self.navigationItem.titleView = nil; - }); - } - else { - self.navigationItem.titleView = nil; - } -} - - - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:_observerWillEnterForeground]; @@ -119,11 +70,6 @@ - (void)viewDidLoad { // registering nibs for current VC and search results VC [self registerNibs]; - CATransition *fadeTextAnimation = [CATransition animation]; - fadeTextAnimation.duration = 1.0; - fadeTextAnimation.type = kCATransitionFade; - [self.navigationController.navigationBar.layer addAnimation: fadeTextAnimation - forKey:@"fadeText"]; [self performAutoLoginAndFetchData]; // adding refresh control task if (self.refreshControl) { @@ -142,7 +88,7 @@ - (void)viewDidLoad { queue:nil usingBlock:^(NSNotification * _Nonnull __unused note) { - + @strongify(self); if ([QBChat instance].isConnected) { // if chat was connected (e.g. we are in call) in background // we skip requests, so perform them now as app is active now @@ -150,7 +96,9 @@ - (void)viewDidLoad { [QMTasks taskUpdateContacts]; } else { - [self performAutoLoginAndFetchData]; + [(QMNavigationController *)self.navigationController showNotificationWithType:QMNotificationPanelTypeLoading + message:NSLocalizedString(@"QM_STR_CONNECTING", nil) + duration:0]; } }]; @@ -199,6 +147,11 @@ - (void)viewWillAppear:(BOOL)animated { - (void)performAutoLoginAndFetchData { + QMNavigationController *navigationController = (id)self.navigationController; + [navigationController showNotificationWithType:QMNotificationPanelTypeLoading + message:NSLocalizedString(@"QM_STR_CONNECTING", nil) + duration:0]; + if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground && !QBChat.instance.manualInitialPresence) { // connecting to chat with manual initial presence if in the background @@ -206,23 +159,15 @@ - (void)performAutoLoginAndFetchData { QBChat.instance.manualInitialPresence = YES; } - if (self.loginTask) { - return; - } - - [self showLoadingWithStatus:NSLocalizedString(@"QM_STR_CONNECTING", nil)]; - - self.loginTask = [[QMCore.instance login] continueWithBlock:^id(BFTask *task) { + [[QMCore.instance login] continueWithBlock:^id(BFTask *task) { - //Perform logout task in case user is not athorized or facebook session is invalidated if (task.isFaulted) { - [self dismissLoadingWithResultStatus:nil]; + [navigationController dismissNotificationPanel]; NSInteger errorCode = task.error.code; if (errorCode == kQMNotAuthorizedInRest || errorCode == kQMUnauthorizedErrorCode - || isFacebookError(task.error) || (errorCode == kBFMultipleErrorsError && ([task.error.userInfo[BFTaskMultipleErrorsUserInfoKey][0] code] == kQMUnauthorizedErrorCode || [task.error.userInfo[BFTaskMultipleErrorsUserInfoKey][1] code] == kQMUnauthorizedErrorCode))) { @@ -234,9 +179,6 @@ - (void)performAutoLoginAndFetchData { } } - [QMTasks taskFetchAllData]; - [QMTasks taskUpdateContacts]; - if (QMCore.instance.pushNotificationManager.pushNotification != nil) { [QMCore.instance.pushNotificationManager handlePushNotificationWithDelegate:self]; } @@ -246,7 +188,6 @@ - (void)performAutoLoginAndFetchData { } return nil; - }]; } @@ -463,7 +404,11 @@ - (void)chatServiceChatHasStartedConnecting:(QMChatService *)__unused chatServic } - (void)chatServiceChatDidConnect:(QMChatService *)__unused chatService { - [self dismissLoadingWithResultStatus:nil]; + + [(QMNavigationController *)self.navigationController + showNotificationWithType:QMNotificationPanelTypeSuccess + message:NSLocalizedString(@"QM_STR_CHAT_CONNECTED", nil) + duration:kQMDefaultNotificationDismissTime]; } - (void)chatServiceChatDidReconnect:(QMChatService *)__unused chatService { @@ -472,7 +417,10 @@ - (void)chatServiceChatDidReconnect:(QMChatService *)__unused chatService { [QMTasks taskFetchAllData]; } - [self dismissLoadingWithResultStatus:nil]; + [(QMNavigationController *)self.navigationController + showNotificationWithType:QMNotificationPanelTypeSuccess + message:NSLocalizedString(@"QM_STR_CHAT_RECONNECTED", nil) + duration:kQMDefaultNotificationDismissTime]; } - (void)contactListService:(QMContactListService *)__unused contactListService @@ -507,7 +455,7 @@ - (void)usersService:(QMUsersService *)__unused usersService if ([self.tableView.dataSource isKindOfClass:[QMDialogsDataSource class]]) { [self.tableView reloadData]; - + } } @@ -596,7 +544,7 @@ - (void)updateDataAndEndRefreshing { [[[QMTasks taskFetchAllData] continueWithBlock:^id _Nullable(BFTask * __unused t) { return [QMTasks taskUpdateContacts]; }] continueWithBlock:^id _Nullable(BFTask * __unused t) { - [self.refreshControl endRefreshing]; + [self.refreshControl endRefreshing]; return nil; }]; From f218da6a0ac818d9ee986ddf536ae6989456a2c5 Mon Sep 17 00:00:00 2001 From: Vitalii404 Date: Wed, 17 Jan 2018 19:43:13 +0200 Subject: [PATCH 10/10] fixed issue with facebook and invalidated session; refactored login logic; --- Q-municate/Classes/Core/QMCore/QMCore.h | 10 ++++ Q-municate/Classes/Core/QMCore/QMCore.m | 52 +++++++++++++++++-- Q-municate/Classes/QMTasks/QMTasks.m | 20 +------ Q-municate/QMAppDelegate.m | 2 + .../QMDialogsViewController.m | 41 ++++++--------- 5 files changed, 76 insertions(+), 49 deletions(-) diff --git a/Q-municate/Classes/Core/QMCore/QMCore.h b/Q-municate/Classes/Core/QMCore/QMCore.h index b86502181..0aefa06b9 100644 --- a/Q-municate/Classes/Core/QMCore/QMCore.h +++ b/Q-municate/Classes/Core/QMCore/QMCore.h @@ -74,6 +74,16 @@ QMOpenGraphCacheDataSource, QMOpenGraphServiceDelegate> */ @property (copy, nonatomic, nullable) NSString *activeDialogID; +/** + A block called when login process completes with critical authorization error/errors. + + @discussion Common critical situations: + - An user, who signed in via Facebook, has changed facebook password. + - An user, who signed in via Facebook, has removed application from settings. + - There is no authorized user. + */ +@property (copy, nonatomic, nullable) dispatch_block_t athorizationErrorBlock; + /** * QMCore shared instance. * diff --git a/Q-municate/Classes/Core/QMCore/QMCore.m b/Q-municate/Classes/Core/QMCore/QMCore.m index da5f4ea19..1a778d121 100644 --- a/Q-municate/Classes/Core/QMCore/QMCore.m +++ b/Q-municate/Classes/Core/QMCore/QMCore.m @@ -24,11 +24,15 @@ static NSString *const kQMErrorKey = @"errors"; static NSString *const kQMBaseErrorKey = @"base"; +static const NSInteger kQMNotAuthorizedInRest = -1000; +static const NSInteger kQMUnauthorizedErrorCode = -1011; + static NSString *const kQMContactListCacheNameKey = @"q-municate-contacts"; static NSString *const kQMOpenGraphCacheNameKey = @"q-municate-open-graph"; @interface QMCore () +@property (weak, nonatomic) BFTask *loginTask; @property (strong, nonatomic) NSMutableOrderedSet *cachedVocabularyStrings; @end @@ -211,11 +215,27 @@ - (void)handleErrorResponse:(QBResponse *)response { - (BFTask *)login { - return [[QMTasks taskAutoLogin] - continueWithSuccessBlock:^id(BFTask *task) { - return [self.chatService connectWithUserID:task.result.ID - password:task.result.password]; - }]; + if (self.loginTask) { + return nil; + } + + self.loginTask = + [[QMTasks taskAutoLogin] continueWithBlock:^id _Nullable(BFTask * _Nonnull loginTask) { + + if (loginTask.error) { + if (isCriritalAuthorizationError(loginTask.error) && + self.athorizationErrorBlock) { + self.athorizationErrorBlock(); + self.athorizationErrorBlock = nil; + } + return loginTask; + } + return [self.chatService connectWithUserID:loginTask.result.ID + password:loginTask.result.password]; + }]; + + + return self.loginTask; } - (BFTask *)logout { @@ -435,4 +455,26 @@ - (void)authService:(QMAuthService *)__unused authService } } +static BOOL isCriritalAuthorizationError(NSError *error) { + NSCParameterAssert(error); + NSInteger errorCode = error.code; + if (errorCode == kQMNotAuthorizedInRest + || errorCode == kQMUnauthorizedErrorCode + || isFacebookSessionError(error) + || (errorCode == kBFMultipleErrorsError + && ([error.userInfo[BFTaskMultipleErrorsUserInfoKey][0] code] == kQMUnauthorizedErrorCode + || [error.userInfo[BFTaskMultipleErrorsUserInfoKey][1] code] == kQMUnauthorizedErrorCode))) { + return YES; + } + + return NO; +} + +static BOOL isFacebookSessionError(NSError *error) { + NSString *errorType = + error.userInfo[@"com.facebook.sdk:FBSDKGraphRequestErrorParsedJSONResponseKey"][@"body"][@"error"][@"type"]; + + return [errorType isEqualToString:@"OAuthException"]; +} + @end diff --git a/Q-municate/Classes/QMTasks/QMTasks.m b/Q-municate/Classes/QMTasks/QMTasks.m index 6c6778fdf..2e57d69fe 100644 --- a/Q-municate/Classes/QMTasks/QMTasks.m +++ b/Q-municate/Classes/QMTasks/QMTasks.m @@ -116,18 +116,7 @@ + (BFTask *)taskAutoLogin { } else if (type == QMAccountTypeFacebook) { - return [[QMFacebook connect] continueWithBlock:^id(BFTask *task) { - - NSError *error = task.error; - if (error) { - if (isFacebookSessionError(error)) { - NSError *notLoggedError = [QMErrorsFactory errorNotLoggedInREST]; - return [BFTask taskWithError:notLoggedError]; - } - else { - return task; - } - } + return [[QMFacebook connect] continueWithSuccessBlock:^id(BFTask *task) { return [core.authService loginWithFacebookSessionToken:task.result]; }]; } @@ -298,11 +287,4 @@ + (void)sliceArray:(NSArray *)array return filters; } -static BOOL isFacebookSessionError(NSError *error) { - if (!error) { - return NO; - } - return [error.userInfo[@"error"][@"type"] isEqualToString:@"OAuthException"]; -} - @end diff --git a/Q-municate/QMAppDelegate.m b/Q-municate/QMAppDelegate.m index cb986029f..ccbe82475 100644 --- a/Q-municate/QMAppDelegate.m +++ b/Q-municate/QMAppDelegate.m @@ -117,6 +117,8 @@ - (void)applicationWillEnterForeground:(UIApplication *)__unused application { if (QBChat.instance.manualInitialPresence) { QBChat.instance.manualInitialPresence = NO; } + // connect to chat now + [QMCore.instance login]; } - (void)applicationDidBecomeActive:(UIApplication *)__unused application { diff --git a/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m b/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m index fb2fb88b5..10c414117 100644 --- a/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m +++ b/Q-municate/Scenes/Main/QMDialogsViewController/QMDialogsViewController.m @@ -24,9 +24,6 @@ #import "QMNavigationBar.h" #import -static const NSInteger kQMNotAuthorizedInRest = -1000; -static const NSInteger kQMUnauthorizedErrorCode = -1011; - @interface QMDialogsViewController ()