diff --git a/src/Contacts/Threads/TSGroupThread.h b/src/Contacts/Threads/TSGroupThread.h index a91e79676..56b6d5a83 100644 --- a/src/Contacts/Threads/TSGroupThread.h +++ b/src/Contacts/Threads/TSGroupThread.h @@ -12,6 +12,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) TSGroupModel *groupModel; ++ (instancetype)getOrCreateThreadWithGroupModel:(TSGroupModel *)groupModel; + (instancetype)getOrCreateThreadWithGroupModel:(TSGroupModel *)groupModel transaction:(YapDatabaseReadWriteTransaction *)transaction; diff --git a/src/Contacts/Threads/TSGroupThread.m b/src/Contacts/Threads/TSGroupThread.m index 3783fdaa5..327ce7f0f 100644 --- a/src/Contacts/Threads/TSGroupThread.m +++ b/src/Contacts/Threads/TSGroupThread.m @@ -5,6 +5,7 @@ #import "NSData+Base64.h" #import "SignalRecipient.h" #import "TSAttachmentStream.h" +#import #import NS_ASSUME_NONNULL_BEGIN @@ -65,6 +66,15 @@ NS_ASSUME_NONNULL_BEGIN return thread; } ++ (instancetype)getOrCreateThreadWithGroupModel:(TSGroupModel *)groupModel +{ + __block TSGroupThread *thread; + [[self dbConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + thread = [self getOrCreateThreadWithGroupModel:groupModel transaction:transaction]; + }]; + return thread; +} + + (NSString *)threadIdFromGroupId:(NSData *)groupId { return [TSGroupThreadPrefix stringByAppendingString:[groupId base64EncodedString]]; diff --git a/src/Messages/OWSMessageSender.m b/src/Messages/OWSMessageSender.m index 17086e0fb..08f429883 100644 --- a/src/Messages/OWSMessageSender.m +++ b/src/Messages/OWSMessageSender.m @@ -348,16 +348,28 @@ NSString *const OWSMessageSenderInvalidDeviceException = @"InvalidDeviceExceptio }]; [completionFuture catchDo:^(id failure) { - if ([failure isKindOfClass:[NSError class]]) { - return failureHandler(failure); - } else if ([failure isKindOfClass:[NSArray class]]) { + // failure from toc_thenAll yeilds an array of failed Futures, rather than the future's failure. + if ([failure isKindOfClass:[NSArray class]]) { NSArray *errors = (NSArray *)failure; - // failure from toc_thenAll is a Future, rather than the future's failure. - if ([errors.lastObject isKindOfClass:[TOCFuture class]]) { - TOCFuture *lastFailure = (TOCFuture *)errors.lastObject; - id failureResult = lastFailure.forceGetFailure; - if ([failureResult isKindOfClass:[NSError class]]) { - return failureHandler((NSError *)failureResult); + for (TOCFuture *failedFuture in errors) { + if (!failedFuture.hasFailed) { + // If at least one send succeeded, don't show message as failed. + // Else user will tap-to-resend to all recipients, including those that already received the + // message. + return successHandler(); + } + } + + // At this point, all recipients must have failed. + // But we have all this verbose type checking because TOCFuture doesn't expose type information. + id lastError = errors.lastObject; + if ([lastError isKindOfClass:[TOCFuture class]]) { + TOCFuture *failedFuture = (TOCFuture *)lastError; + if (failedFuture.hasFailed) { + id failureResult = failedFuture.forceGetFailure; + if ([failureResult isKindOfClass:[NSError class]]) { + return failureHandler((NSError *)failureResult); + } } } } @@ -426,8 +438,17 @@ NSString *const OWSMessageSenderInvalidDeviceException = @"InvalidDeviceExceptio void (^retrySend)() = ^void() { if (remainingAttempts <= 0) { - return failureHandler(error); + NSError *presentedError = error; + if (statuscode == 409) { + presentedError = OWSErrorWithCodeDescription(OWSErrorCodeUntrustedIdentityKey, + NSLocalizedString(@"FAILED_SENDING_BECAUSE_UNTRUSTED_IDENTITY_KEY", + @"action sheet header when re-sending message which failed because of untrusted " + @"identity keys")); + } + + return failureHandler(presentedError); } + dispatch_async([OWSDispatch sendingQueue], ^{ [self sendMessage:message recipient:recipient diff --git a/src/Util/OWSError.h b/src/Util/OWSError.h index 39288cc2b..4614192ad 100644 --- a/src/Util/OWSError.h +++ b/src/Util/OWSError.h @@ -11,6 +11,7 @@ typedef NS_ENUM(NSInteger, OWSErrorCode) { OWSErrorCodeFailedToEncodeJson = 14, OWSErrorCodeFailedToDecodeQR = 15, OWSErrorCodePrivacyVerificationFailure = 20, + OWSErrorCodeUntrustedIdentityKey = 25, OWSErrorCodeFailedToSendOutgoingMessage = 30, OWSErrorCodeFailedToDecryptMessage = 100 }; diff --git a/tests/Messages/OWSMessageSenderTest.m b/tests/Messages/OWSMessageSenderTest.m index 2d6de79fa..8b725daf6 100644 --- a/tests/Messages/OWSMessageSenderTest.m +++ b/tests/Messages/OWSMessageSenderTest.m @@ -1,6 +1,7 @@ // Created by Michael Kirk on 10/7/16. // Copyright © 2016 Open Whisper Systems. All rights reserved. +#import "Cryptography.h" #import "OWSError.h" #import "OWSFakeContactsManager.h" #import "OWSFakeContactsUpdater.h" @@ -8,11 +9,15 @@ #import "OWSMessageSender.h" #import "OWSUploadingService.h" #import "TSContactThread.h" +#import "TSGroupModel.h" +#import "TSGroupThread.h" #import "TSMessagesManager.h" #import "TSNetworkManager.h" #import "TSOutgoingMessage.h" #import "TSStorageManager+keyingMaterial.h" #import "TSStorageManager.h" +#import +#import #import NS_ASSUME_NONNULL_BEGIN @@ -393,6 +398,44 @@ NS_ASSUME_NONNULL_BEGIN [self waitForExpectationsWithTimeout:5 handler:nil]; } +- (void)testGroupSend +{ + OWSMessageSender *messageSender = self.successfulMessageSender; + + + NSData *groupIdData = [Cryptography generateRandomBytes:32]; + SignalRecipient *successfulRecipient = + [[SignalRecipient alloc] initWithTextSecureIdentifier:@"successful-recipient-id" relay:nil supportsVoice:YES]; + SignalRecipient *successfulRecipient2 = + [[SignalRecipient alloc] initWithTextSecureIdentifier:@"successful-recipient-id2" relay:nil supportsVoice:YES]; + + TSGroupModel *groupModel = [[TSGroupModel alloc] + initWithTitle:@"group title" + memberIds:[@[ successfulRecipient.uniqueId, successfulRecipient2.uniqueId ] mutableCopy] + image:nil + groupId:groupIdData]; + TSGroupThread *groupThread = [TSGroupThread getOrCreateThreadWithGroupModel:groupModel]; + TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:1 + inThread:groupThread + messageBody:@"We want punks in the palace."]; + + XCTestExpectation *markedAsSent = [self expectationWithDescription:@"markedAsSent"]; + [messageSender sendMessage:message + success:^{ + if (message.messageState == TSOutgoingMessageStateSent) { + [markedAsSent fulfill]; + } else { + XCTFail(@"Unexpected message state"); + } + + } + failure:^(NSError *_Nonnull error) { + XCTFail(@"sendMessage should not fail."); + }]; + + [self waitForExpectationsWithTimeout:5 handler:nil]; +} + - (void)testGetRecipients { SignalRecipient *recipient =