Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Q-municate/Categories/QBSettings/QBSettings+Qmunicate.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ typedef NS_ENUM(NSUInteger, QMApplicationZone) {
QMApplicationZoneQA,
};

static const QMApplicationZone QMCurrentApplicationZone = QMApplicationZoneDevelopment;
static const QMApplicationZone QMCurrentApplicationZone = QMApplicationZoneQA;

@interface QBSettings (Qmunicate)

Expand Down
10 changes: 10 additions & 0 deletions Q-municate/Classes/Core/QMCore/QMCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
58 changes: 53 additions & 5 deletions Q-municate/Classes/Core/QMCore/QMCore.m
Original file line number Diff line number Diff line change
Expand Up @@ -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 () <QMAuthServiceDelegate>

@property (weak, nonatomic) BFTask *loginTask;
@property (strong, nonatomic) NSMutableOrderedSet *cachedVocabularyStrings;

@end
Expand Down Expand Up @@ -98,8 +102,13 @@ - (void)configureReachability {
@weakify(self);
[_internetConnection setReachableBlock:^(Reachability __unused *reachability) {

@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];
});
Expand Down Expand Up @@ -206,10 +215,27 @@ - (void)handleErrorResponse:(QBResponse *)response {

- (BFTask *)login {

return [[QMTasks taskAutoLogin]
continueWithSuccessBlock:^id(BFTask<QBUUser *> *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<QBUUser *> * _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 {
Expand Down Expand Up @@ -432,4 +458,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
85 changes: 51 additions & 34 deletions Q-municate/Classes/QMFacebook/QMFacebook.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,46 +18,61 @@
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 =
(id)[[UIApplication sharedApplication].windows.firstObject rootViewController];

UINavigationController *navigationController = (UINavigationController *)[[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<NSDictionary *> * _Nonnull __unused t) {
return [BFTask taskWithResult:session.tokenString];
}];
}

return source.task;
}

+ (NSURL *)userImageUrlWithUserID:(NSString *)userID {
Expand All @@ -70,16 +85,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 setGraphErrorRecoveryDisabled:YES];
[myInfoRequest startWithCompletionHandler:^(FBSDKGraphRequestConnection *__unused connection,
id result,
NSError *error)
{
error ? [source setError:error] : [source setResult:result];
}];
});
}

+ (void)logout {
Expand Down
30 changes: 15 additions & 15 deletions Q-municate/Classes/QMTasks/QMTasks.m
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ + (BFTask *)taskAutoLogin {
}
else if (type == QMAccountTypeFacebook) {

return [[QMFacebook connect] continueWithBlock:^id(BFTask<NSString *> *task) {
return task.result ? [core.authService loginWithFacebookSessionToken:task.result] : nil;
return [[QMFacebook connect] continueWithSuccessBlock:^id(BFTask<NSString *> *task) {
return [core.authService loginWithFacebookSessionToken:task.result];
}];
}
else if (type == QMAccountTypePhone) {
Expand Down Expand Up @@ -168,10 +168,10 @@ + (BFTask *)taskFetchAllData {
[self sliceArray:dialogsUsersIDs.allObjects
limit:kQMUsersPageLimit
enumerate:^(NSArray *slice, NSRange __unused range)
{
BFTask<NSArray<QBUUser *> *> *task = [core.usersService getUsersWithIDs:slice];
[usersLoadingTasks addObject:task];
}];
{
BFTask<NSArray<QBUUser *> *> *task = [core.usersService getUsersWithIDs:slice];
[usersLoadingTasks addObject:task];
}];
}
};

Expand Down Expand Up @@ -234,15 +234,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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
#import "QMNavigationBar.h"
#import <notify.h>

static const NSInteger kQMNotAuthorizedInRest = -1000;
static const NSInteger kQMUnauthorizedErrorCode = -1011;

@interface QMDialogsViewController ()

<QMUsersServiceDelegate, QMChatServiceDelegate, QMChatConnectionDelegate,
Expand All @@ -41,8 +38,6 @@ @interface QMDialogsViewController ()
@property (strong, nonatomic) QMDialogsDataSource *dialogsDataSource;
@property (strong, nonatomic) QMDialogsSearchDataSource *dialogsSearchDataSource;

@property (weak, nonatomic) BFTask *addUserTask;

@property (strong, nonatomic) id observerWillEnterForeground;

@end
Expand All @@ -65,6 +60,14 @@ - (void)viewDidLoad {
[QMCore.instance.chatService addDelegate:self];
[QMCore.instance.usersService addDelegate:self];
[QMCore.instance.contactListService addDelegate:self];

QMCore.instance.athorizationErrorBlock = ^{
[[QMCore.instance logout] continueWithBlock:^id _Nullable(BFTask * _Nonnull __unused t) {
[self performSegueWithIdentifier:kQMSceneSegueAuth sender:nil];
return nil;
}];
};

// search implementation
[self configureSearch];
// Data sources init
Expand Down Expand Up @@ -109,7 +112,7 @@ - (void)viewDidLoad {

NSDate *lastFetchDate =
QMCore.instance.currentProfile.lastDialogsFetchingDate;
@strongify(self);
@strongify(self);
[[QMCore.instance.chatService syncLaterDialogsWithCacheFromDate:lastFetchDate] continueWithBlock:^id _Nullable(BFTask<NSArray<QBChatDialog *> *> * _Nonnull t)
{
if (t.result.count > 0) {
Expand Down Expand Up @@ -162,37 +165,18 @@ - (void)performAutoLoginAndFetchData {
QBChat.instance.manualInitialPresence = YES;
}

[[[QMCore.instance login] continueWithBlock:^id(BFTask *task) {

[[QMCore.instance login] continueWithBlock:^id(BFTask *task) {
if (task.isFaulted) {

[navigationController dismissNotificationPanel];

NSInteger errorCode = task.error.code;
if (errorCode == kQMNotAuthorizedInRest
|| errorCode == kQMUnauthorizedErrorCode
|| (errorCode == kBFMultipleErrorsError
&& ([task.error.userInfo[BFTaskMultipleErrorsUserInfoKey][0] code] == kQMUnauthorizedErrorCode
|| [task.error.userInfo[BFTaskMultipleErrorsUserInfoKey][1] code] == kQMUnauthorizedErrorCode))) {

return [QMCore.instance logout];
}
}

if (QMCore.instance.pushNotificationManager.pushNotification != nil) {
[QMCore.instance.pushNotificationManager handlePushNotificationWithDelegate:self];
}

if (QMCore.instance.currentProfile.pushNotificationsEnabled) {
[QMCore.instance.pushNotificationManager registerAndSubscribeForPushNotifications];
}

return [BFTask cancelledTask];

}] continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {

if (!task.isCancelled) {
[self performSegueWithIdentifier:kQMSceneSegueAuth sender:nil];
else {
if (QMCore.instance.pushNotificationManager.pushNotification != nil) {
[QMCore.instance.pushNotificationManager handlePushNotificationWithDelegate:self];
}

if (QMCore.instance.currentProfile.pushNotificationsEnabled) {
[QMCore.instance.pushNotificationManager registerAndSubscribeForPushNotifications];
}
}

return nil;
Expand Down Expand Up @@ -463,7 +447,7 @@ - (void)usersService:(QMUsersService *)__unused usersService

if ([self.tableView.dataSource isKindOfClass:[QMDialogsDataSource class]]) {
[self.tableView reloadData];

}
}

Expand Down Expand Up @@ -552,7 +536,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;
}];

Expand Down
2 changes: 1 addition & 1 deletion QMShareExtension/Common classes/QMShareTasks.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down