session-ios/SignalServiceKit/src/Messages/OWSMessageSender.m

2070 lines
86 KiB
Mathematica
Raw Normal View History

//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "OWSMessageSender.h"
#import "AppContext.h"
#import "NSData+keyVersionByte.h"
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
#import "NSData+messagePadding.h"
#import "NSError+MessageSending.h"
2017-12-15 19:03:03 +01:00
#import "OWSBackgroundTask.h"
2017-04-03 20:42:04 +02:00
#import "OWSBlockingManager.h"
#import "OWSContact.h"
2017-02-16 00:32:27 +01:00
#import "OWSDevice.h"
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
#import "OWSDisappearingMessagesJob.h"
#import "OWSDispatch.h"
#import "OWSError.h"
#import "OWSIdentityManager.h"
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
#import "OWSMessageServiceParams.h"
#import "OWSOperation.h"
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
#import "OWSOutgoingSentMessageTranscript.h"
#import "OWSOutgoingSyncMessage.h"
#import "OWSPrimaryStorage+PreKeyStore.h"
#import "OWSPrimaryStorage+SignedPreKeyStore.h"
#import "OWSPrimaryStorage+sessionStore.h"
#import "OWSPrimaryStorage+Loki.h"
#import "OWSPrimaryStorage.h"
2018-03-02 04:29:59 +01:00
#import "OWSRequestFactory.h"
#import "OWSUploadOperation.h"
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
#import "PreKeyBundle+jsonDict.h"
#import "SSKEnvironment.h"
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
#import "SignalRecipient.h"
#import "TSAccountManager.h"
#import "TSAttachmentStream.h"
#import "TSContactThread.h"
#import "TSGroupThread.h"
#import "TSIncomingMessage.h"
#import "TSInfoMessage.h"
#import "TSInvalidIdentityKeySendingErrorMessage.h"
#import "TSNetworkManager.h"
#import "TSOutgoingMessage.h"
2017-02-10 19:20:11 +01:00
#import "TSPreKeyManager.h"
#import "TSQuotedMessage.h"
2018-10-03 14:55:14 +02:00
#import "TSRequest.h"
2018-05-18 19:55:22 +02:00
#import "TSSocketManager.h"
#import "TSThread.h"
#import "OWSFriendRequestMessage.h"
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
#import <AxolotlKit/AxolotlExceptions.h>
#import <AxolotlKit/CipherMessage.h>
#import <AxolotlKit/PreKeyBundle.h>
#import <AxolotlKit/SessionBuilder.h>
#import <AxolotlKit/SessionCipher.h>
2018-07-20 21:22:51 +02:00
#import <PromiseKit/AnyPromise.h>
2018-10-03 14:55:14 +02:00
#import <SignalCoreKit/NSData+OWS.h>
2018-09-21 21:41:10 +02:00
#import <SignalCoreKit/NSDate+OWS.h>
2018-10-28 19:18:04 +01:00
#import <SignalCoreKit/SCKExceptionWrapper.h>
2018-09-21 21:41:10 +02:00
#import <SignalCoreKit/Threading.h>
2018-10-03 14:55:14 +02:00
#import <SignalMetadataKit/SignalMetadataKit-Swift.h>
2018-07-20 21:22:51 +02:00
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
NSString *NoSessionForTransientMessageException = @"NoSessionForTransientMessageException";
2018-04-11 21:21:17 +02:00
const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024;
2018-10-04 20:39:40 +02:00
NSError *SSKEnsureError(NSError *_Nullable error, OWSErrorCode fallbackCode, NSString *fallbackErrorDescription)
{
if (error) {
return error;
}
2018-10-05 18:53:54 +02:00
OWSCFailDebug(@"Using fallback error.");
return OWSErrorWithCodeDescription(fallbackCode, fallbackErrorDescription);
2018-10-04 20:39:40 +02:00
}
#pragma mark -
void AssertIsOnSendingQueue()
{
2017-04-07 18:46:42 +02:00
#ifdef DEBUG
if (@available(iOS 10.0, *)) {
dispatch_assert_queue([OWSDispatch sendingQueue]);
} // else, skip assert as it's a development convenience.
2017-04-07 18:46:42 +02:00
#endif
}
#pragma mark -
2018-11-02 16:33:05 +01:00
@implementation OWSOutgoingAttachmentInfo
- (instancetype)initWithDataSource:(DataSource *)dataSource
contentType:(NSString *)contentType
sourceFilename:(nullable NSString *)sourceFilename
caption:(nullable NSString *)caption
2018-11-07 18:00:34 +01:00
albumMessageId:(nullable NSString *)albumMessageId
2018-11-02 16:33:05 +01:00
{
self = [super init];
if (!self) {
return self;
}
_dataSource = dataSource;
_contentType = contentType;
_sourceFilename = sourceFilename;
_caption = caption;
2018-11-07 18:00:34 +01:00
_albumMessageId = albumMessageId;
2018-11-02 16:33:05 +01:00
return self;
}
@end
#pragma mark -
/**
* OWSSendMessageOperation encapsulates all the work associated with sending a message, e.g. uploading attachments,
* getting proper keys, and retrying upon failure.
*
* Used by `OWSMessageSender` to serialize message sending, ensuring that messages are emitted in the order they
* were sent.
*/
@interface OWSSendMessageOperation : OWSOperation
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithMessage:(TSOutgoingMessage *)message
messageSender:(OWSMessageSender *)messageSender
dbConnection:(YapDatabaseConnection *)dbConnection
success:(void (^)(void))aSuccessHandler
2018-07-16 19:11:00 +02:00
failure:(void (^)(NSError * error))aFailureHandler NS_DESIGNATED_INITIALIZER;
@end
#pragma mark -
@interface OWSMessageSender (OWSSendMessageOperation)
- (void)sendMessageToService:(TSOutgoingMessage *)message
2017-11-08 20:04:51 +01:00
success:(void (^)(void))successHandler
failure:(RetryableFailureHandler)failureHandler;
@end
#pragma mark -
@interface OWSSendMessageOperation ()
@property (nonatomic, readonly) TSOutgoingMessage *message;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
@property (nonatomic, readonly) void (^successHandler)(void);
2018-07-16 19:11:00 +02:00
@property (nonatomic, readonly) void (^failureHandler)(NSError * error);
@end
#pragma mark -
@implementation OWSSendMessageOperation
- (instancetype)initWithMessage:(TSOutgoingMessage *)message
messageSender:(OWSMessageSender *)messageSender
dbConnection:(YapDatabaseConnection *)dbConnection
success:(void (^)(void))successHandler
2018-07-16 19:11:00 +02:00
failure:(void (^)(NSError * error))failureHandler
{
self = [super init];
if (!self) {
return self;
}
_message = message;
_messageSender = messageSender;
_dbConnection = dbConnection;
_successHandler = successHandler;
_failureHandler = failureHandler;
return self;
}
#pragma mark - OWSOperation overrides
- (nullable NSError *)checkForPreconditionError
{
2018-11-27 15:54:18 +01:00
__block NSError *_Nullable error = [super checkForPreconditionError];
if (error) {
return error;
}
// Sanity check preconditions
if (self.message.hasAttachments) {
2018-11-02 16:33:05 +01:00
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
for (TSAttachment *attachment in [self.message attachmentsWithTransaction:transaction]) {
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
2018-11-29 20:48:56 +01:00
error = OWSErrorMakeFailedToSendOutgoingMessageError();
2018-11-27 15:54:18 +01:00
break;
}
2018-11-02 16:33:05 +01:00
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment;
OWSAssertDebug(attachmentStream);
OWSAssertDebug(attachmentStream.serverId);
OWSAssertDebug(attachmentStream.isUploaded);
}
}];
}
2018-11-27 15:54:18 +01:00
return error;
}
- (void)run
{
2017-11-14 19:41:01 +01:00
// If the message has been deleted, abort send.
if (self.message.shouldBeSaved && ![TSOutgoingMessage fetchObjectWithUniqueID:self.message.uniqueId]) {
OWSLogInfo(@"aborting message send; message deleted.");
2017-11-14 19:41:01 +01:00
NSError *error = OWSErrorWithCodeDescription(
OWSErrorCodeMessageDeletedBeforeSent, @"Message was deleted before it could be sent.");
error.isFatal = YES;
[self reportError:error];
2017-11-14 19:41:01 +01:00
return;
}
[self.messageSender sendMessageToService:self.message
success:^{
[self reportSuccess];
}
failure:^(NSError *error) {
[self reportError:error];
}];
}
- (void)didSucceed
{
2018-04-30 16:21:58 +02:00
if (self.message.messageState != TSOutgoingMessageStateSent) {
OWSFailDebug(@"unexpected message status: %@", self.message.statusDescription);
2018-04-30 16:21:58 +02:00
}
2018-04-23 16:30:51 +02:00
self.successHandler();
}
2017-06-17 19:41:29 +02:00
- (void)didFailWithError:(NSError *)error
{
OWSLogError(@"failed with error: %@", error);
self.failureHandler(error);
}
@end
2018-10-03 14:55:14 +02:00
#pragma mark -
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
NSString *const OWSMessageSenderInvalidDeviceException = @"InvalidDeviceException";
NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
@interface OWSMessageSender ()
@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage;
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
2017-03-23 19:35:30 +01:00
@property (atomic, readonly) NSMutableDictionary<NSString *, NSOperationQueue *> *sendingQueueMap;
@end
2018-10-03 14:55:14 +02:00
#pragma mark -
@implementation OWSMessageSender
- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage
{
self = [super init];
if (!self) {
return self;
}
_primaryStorage = primaryStorage;
_sendingQueueMap = [NSMutableDictionary new];
_dbConnection = primaryStorage.newDatabaseConnection;
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
OWSSingletonAssert();
return self;
}
2018-10-05 16:32:32 +02:00
#pragma mark - Dependencies
2018-10-03 14:55:14 +02:00
- (id<ContactsManagerProtocol>)contactsManager
{
OWSAssertDebug(SSKEnvironment.shared.contactsManager);
return SSKEnvironment.shared.contactsManager;
}
- (OWSBlockingManager *)blockingManager
{
OWSAssertDebug(SSKEnvironment.shared.blockingManager);
return SSKEnvironment.shared.blockingManager;
}
- (TSNetworkManager *)networkManager
{
OWSAssertDebug(SSKEnvironment.shared.networkManager);
return SSKEnvironment.shared.networkManager;
}
2018-10-03 14:55:14 +02:00
- (id<OWSUDManager>)udManager
{
OWSAssertDebug(SSKEnvironment.shared.udManager);
return SSKEnvironment.shared.udManager;
}
- (TSAccountManager *)tsAccountManager
{
return TSAccountManager.sharedInstance;
}
2018-10-03 23:04:41 +02:00
- (OWSIdentityManager *)identityManager
{
return SSKEnvironment.shared.identityManager;
}
2018-10-03 14:55:14 +02:00
#pragma mark -
- (NSOperationQueue *)sendingQueueForMessage:(TSOutgoingMessage *)message
{
OWSAssertDebug(message);
NSString *kDefaultQueueKey = @"kDefaultQueueKey";
NSString *queueKey = message.uniqueThreadId ?: kDefaultQueueKey;
OWSAssertDebug(queueKey.length > 0);
if ([kDefaultQueueKey isEqualToString:queueKey]) {
// when do we get here?
OWSLogDebug(@"using default message queue");
}
@synchronized(self)
{
NSOperationQueue *sendingQueue = self.sendingQueueMap[queueKey];
if (!sendingQueue) {
sendingQueue = [NSOperationQueue new];
sendingQueue.qualityOfService = NSOperationQualityOfServiceUserInitiated;
sendingQueue.maxConcurrentOperationCount = 1;
2018-09-14 20:02:17 +02:00
sendingQueue.name = [NSString stringWithFormat:@"%@:%@", self.logTag, queueKey];
self.sendingQueueMap[queueKey] = sendingQueue;
}
return sendingQueue;
}
}
- (void)sendMessage:(TSOutgoingMessage *)message
success:(void (^)(void))successHandler
failure:(void (^)(NSError *error))failureHandler
{
OWSAssertDebug(message);
if (message.body.length > 0) {
OWSAssertDebug([message.body lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= kOversizeTextMessageSizeThreshold);
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
2019-01-17 17:56:21 +01:00
NSMutableArray<NSString *> *allAttachmentIds = [NSMutableArray new];
// This method will use a read/write transaction. This transaction
// will block until any open read/write transactions are complete.
//
// That's key - we don't want to send any messages in response
// to an incoming message until processing of that batch of messages
2017-09-21 23:25:13 +02:00
// is complete. For example, we wouldn't want to auto-reply to a
// group info request before that group info request's batch was
// finished processing. Otherwise, we might receive a delivery
// notice for a group update we hadn't yet saved to the db.
//
// So we're using YDB behavior to ensure this invariant, which is a bit
// unorthodox.
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
2019-01-17 17:56:21 +01:00
[allAttachmentIds
addObjectsFromArray:[OutgoingMessagePreparer prepareMessageForSending:message transaction:transaction]];
}];
NSOperationQueue *sendingQueue = [self sendingQueueForMessage:message];
OWSSendMessageOperation *sendMessageOperation =
[[OWSSendMessageOperation alloc] initWithMessage:message
messageSender:self
dbConnection:self.dbConnection
success:successHandler
failure:failureHandler];
2019-01-17 17:56:21 +01:00
for (NSString *attachmentId in allAttachmentIds) {
OWSUploadOperation *uploadAttachmentOperation =
2018-11-02 16:37:35 +01:00
[[OWSUploadOperation alloc] initWithAttachmentId:attachmentId dbConnection:self.dbConnection];
2019-01-17 17:56:21 +01:00
// TODO: put attachment uploads on a (low priority) concurrent queue
[sendMessageOperation addDependency:uploadAttachmentOperation];
[sendingQueue addOperation:uploadAttachmentOperation];
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
[sendingQueue addOperation:sendMessageOperation];
});
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
- (void)sendTemporaryAttachment:(DataSource *)dataSource
contentType:(NSString *)contentType
inMessage:(TSOutgoingMessage *)message
success:(void (^)(void))successHandler
failure:(void (^)(NSError *error))failureHandler
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
OWSAssertDebug(dataSource);
void (^successWithDeleteHandler)(void) = ^() {
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
successHandler();
OWSLogDebug(@"Removing successful temporary attachment message with attachment ids: %@", message.attachmentIds);
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
[message remove];
};
void (^failureWithDeleteHandler)(NSError *error) = ^(NSError *error) {
failureHandler(error);
OWSLogDebug(@"Removing failed temporary attachment message with attachment ids: %@", message.attachmentIds);
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
[message remove];
};
[self sendAttachment:dataSource
contentType:contentType
sourceFilename:nil
albumMessageId:nil
inMessage:message
success:successWithDeleteHandler
failure:failureWithDeleteHandler];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
- (void)sendAttachment:(DataSource *)dataSource
contentType:(NSString *)contentType
sourceFilename:(nullable NSString *)sourceFilename
albumMessageId:(nullable NSString *)albumMessageId
inMessage:(TSOutgoingMessage *)message
success:(void (^)(void))success
failure:(void (^)(NSError *error))failure
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
OWSAssertDebug(dataSource);
2018-11-07 18:00:34 +01:00
2018-11-02 16:33:05 +01:00
OWSOutgoingAttachmentInfo *attachmentInfo = [[OWSOutgoingAttachmentInfo alloc] initWithDataSource:dataSource
contentType:contentType
sourceFilename:sourceFilename
2018-11-07 18:00:34 +01:00
caption:nil
albumMessageId:albumMessageId];
[self sendAttachments:@[
attachmentInfo,
]
inMessage:message
success:success
failure:failure];
}
- (void)sendAttachments:(NSArray<OWSOutgoingAttachmentInfo *> *)attachmentInfos
inMessage:(TSOutgoingMessage *)message
success:(void (^)(void))success
failure:(void (^)(NSError *error))failure
{
OWSAssertDebug(attachmentInfos.count > 0);
[OutgoingMessagePreparer prepareAttachments:attachmentInfos
2018-11-02 16:33:05 +01:00
inMessage:message
completionHandler:^(NSError *_Nullable error) {
if (error) {
failure(error);
2018-11-02 16:33:05 +01:00
return;
}
[self sendMessage:message success:success failure:failure];
2018-11-02 16:33:05 +01:00
}];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
- (void)sendMessageToService:(TSOutgoingMessage *)message
2018-10-03 23:04:41 +02:00
success:(void (^)(void))success
failure:(RetryableFailureHandler)failure
{
[self.udManager
ensureSenderCertificateWithSuccess:^(SMKSenderCertificate *senderCertificate) {
2018-10-04 20:39:40 +02:00
dispatch_async([OWSDispatch sendingQueue], ^{
[self sendMessageToService:message senderCertificate:senderCertificate success:success failure:failure];
});
2018-10-03 23:04:41 +02:00
}
failure:^(NSError *error) {
OWSLogError(@"Could not obtain UD sender certificate: %@", error);
// Proceed using non-UD message sends.
2018-10-04 20:39:40 +02:00
dispatch_async([OWSDispatch sendingQueue], ^{
[self sendMessageToService:message senderCertificate:nil success:success failure:failure];
});
2018-10-03 23:04:41 +02:00
}];
}
2018-10-04 20:39:40 +02:00
- (nullable NSArray<NSString *> *)unsentRecipientsForMessage:(TSOutgoingMessage *)message
thread:(nullable TSThread *)thread
error:(NSError **)errorHandle
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
2018-10-04 20:39:40 +02:00
OWSAssertDebug(message);
OWSAssertDebug(errorHandle);
2018-04-23 16:30:51 +02:00
2018-10-04 20:39:40 +02:00
NSMutableSet<NSString *> *recipientIds = [NSMutableSet new];
if ([message isKindOfClass:[OWSOutgoingSyncMessage class]]) {
2018-12-21 20:15:19 +01:00
[recipientIds addObject:self.tsAccountManager.localNumber];
2018-10-04 20:39:40 +02:00
} else if (thread.isGroupThread) {
TSGroupThread *groupThread = (TSGroupThread *)thread;
2018-10-04 20:39:40 +02:00
// Send to the intersection of:
//
// * "sending" recipients of the message.
// * members of the group.
//
// I.e. try to send a message IFF:
//
// * The recipient was in the group when the message was first tried to be sent.
// * The recipient is still in the group.
// * The recipient is in the "sending" state.
2018-10-04 20:39:40 +02:00
[recipientIds addObjectsFromArray:message.sendingRecipientIds];
// Only send to members in the latest known group member list.
[recipientIds intersectSet:[NSSet setWithArray:groupThread.groupModel.groupMemberIds]];
2018-12-21 20:15:19 +01:00
if ([recipientIds containsObject:self.tsAccountManager.localNumber]) {
2018-10-04 20:39:40 +02:00
OWSFailDebug(@"Message send recipients should not include self.");
}
} else if ([thread isKindOfClass:[TSContactThread class]]) {
NSString *recipientContactId = ((TSContactThread *)thread).contactIdentifier;
// Treat 1:1 sends to blocked contacts as failures.
// If we block a user, don't send 1:1 messages to them. The UI
// should prevent this from occurring, but in some edge cases
// you might, for example, have a pending outgoing message when
// you block them.
OWSAssertDebug(recipientContactId.length > 0);
if ([self.blockingManager isRecipientIdBlocked:recipientContactId]) {
OWSLogInfo(@"skipping 1:1 send to blocked contact: %@", recipientContactId);
2018-10-05 18:00:31 +02:00
NSError *error = OWSErrorMakeMessageSendFailedDueToBlockListError();
[error setIsRetryable:NO];
2018-10-04 20:39:40 +02:00
*errorHandle = error;
return nil;
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
2018-10-03 14:55:14 +02:00
2018-10-04 20:39:40 +02:00
[recipientIds addObject:recipientContactId];
2018-10-03 14:55:14 +02:00
2018-12-21 20:15:19 +01:00
if ([recipientIds containsObject:self.tsAccountManager.localNumber]) {
2018-10-04 20:39:40 +02:00
OWSFailDebug(@"Message send recipients should not include self.");
2018-10-03 14:55:14 +02:00
}
2018-10-04 20:39:40 +02:00
} else {
// Neither a group nor contact thread? This should never happen.
OWSFailDebug(@"Unknown message type: %@", [message class]);
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
[error setIsRetryable:NO];
*errorHandle = error;
return nil;
}
2018-10-03 14:55:14 +02:00
2018-10-04 20:39:40 +02:00
[recipientIds minusSet:[NSSet setWithArray:self.blockingManager.blockedPhoneNumbers]];
return recipientIds.allObjects;
}
2018-10-03 14:55:14 +02:00
2018-10-04 20:39:40 +02:00
- (NSArray<SignalRecipient *> *)recipientsForRecipientIds:(NSArray<NSString *> *)recipientIds
{
OWSAssertDebug(recipientIds.count > 0);
NSMutableArray<SignalRecipient *> *recipients = [NSMutableArray new];
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
for (NSString *recipientId in recipientIds) {
SignalRecipient *recipient =
[SignalRecipient getOrBuildUnsavedRecipientForRecipientId:recipientId transaction:transaction];
[recipients addObject:recipient];
}
}];
return [recipients copy];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
2018-10-04 20:39:40 +02:00
- (AnyPromise *)sendPromiseForRecipients:(NSArray<SignalRecipient *> *)recipients
message:(TSOutgoingMessage *)message
thread:(nullable TSThread *)thread
senderCertificate:(nullable SMKSenderCertificate *)senderCertificate
sendErrors:(NSMutableArray<NSError *> *)sendErrors
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
2018-10-04 20:39:40 +02:00
OWSAssertDebug(recipients.count > 0);
OWSAssertDebug(message);
OWSAssertDebug(sendErrors);
2018-04-24 22:11:10 +02:00
2018-07-20 21:22:51 +02:00
NSMutableArray<AnyPromise *> *sendPromises = [NSMutableArray array];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
2018-10-04 20:39:40 +02:00
for (SignalRecipient *recipient in recipients) {
// Use chained promises to make the code more readable.
2018-07-20 21:22:51 +02:00
AnyPromise *sendPromise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
2018-11-07 20:16:51 +01:00
NSString *localNumber = self.tsAccountManager.localNumber;
2018-10-30 17:06:20 +01:00
OWSUDAccess *_Nullable theirUDAccess;
2018-11-07 20:16:51 +01:00
if (senderCertificate != nil && ![recipient.recipientId isEqualToString:localNumber]) {
2018-10-30 18:25:01 +01:00
theirUDAccess = [self.udManager udAccessForRecipientId:recipient.recipientId requireSyncAccess:YES];
}
2018-10-04 20:39:40 +02:00
OWSMessageSend *messageSend = [[OWSMessageSend alloc] initWithMessage:message
thread:thread
recipient:recipient
senderCertificate:senderCertificate
2018-10-30 17:06:20 +01:00
udAccess:theirUDAccess
2018-10-04 20:39:40 +02:00
localNumber:self.tsAccountManager.localNumber
2018-07-20 21:22:51 +02:00
success:^{
2018-07-23 19:25:21 +02:00
// The value doesn't matter, we just need any non-NSError value.
2018-07-20 21:22:51 +02:00
resolve(@(1));
}
failure:^(NSError *error) {
@synchronized(sendErrors) {
[sendErrors addObject:error];
}
resolve(error);
}];
2018-10-04 20:39:40 +02:00
[self sendMessageToRecipient:messageSend];
2018-07-20 21:22:51 +02:00
}];
[sendPromises addObject:sendPromise];
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
2018-07-20 21:22:51 +02:00
// We use PMKJoin(), not PMKWhen(), because we don't want the
// completion promise to execute until _all_ send promises
// have either succeeded or failed. PMKWhen() executes as
// soon as any of its input promises fail.
2018-10-04 20:39:40 +02:00
return PMKJoin(sendPromises);
}
- (void)sendMessageToService:(TSOutgoingMessage *)message
senderCertificate:(nullable SMKSenderCertificate *)senderCertificate
success:(void (^)(void))successHandlerParam
failure:(RetryableFailureHandler)failureHandlerParam
2018-10-04 20:39:40 +02:00
{
AssertIsOnSendingQueue();
void (^successHandler)(void) = ^() {
dispatch_async([OWSDispatch sendingQueue], ^{
2018-10-24 16:53:23 +02:00
[self handleMessageSentLocally:message
success:^{
successHandlerParam();
}
failure:^(NSError *error) {
OWSLogError(@"Error sending sync message for message: %@ timestamp: %llu",
message.class,
message.timestamp);
failureHandlerParam(error);
}];
});
};
void (^failureHandler)(NSError *) = ^(NSError *error) {
if (message.wasSentToAnyRecipient) {
dispatch_async([OWSDispatch sendingQueue], ^{
2018-10-24 16:53:23 +02:00
[self handleMessageSentLocally:message
success:^{
failureHandlerParam(error);
}
failure:^(NSError *syncError) {
OWSLogError(@"Error sending sync message for message: %@ timestamp: %llu, %@",
message.class,
message.timestamp,
syncError);
// Discard the "sync message" error in favor of the
// original error.
failureHandlerParam(error);
}];
});
2018-10-24 16:53:23 +02:00
return;
}
failureHandlerParam(error);
};
2018-11-26 16:00:25 +01:00
TSThread *_Nullable thread = message.thread;
2018-11-21 20:54:32 +01:00
BOOL isSyncMessage = [message isKindOfClass:[OWSOutgoingSyncMessage class]];
if (!thread && !isSyncMessage) {
OWSFailDebug(@"Missing thread for non-sync message.");
// This thread has been deleted since the message was enqueued.
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageSendNoValidRecipients,
NSLocalizedString(@"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS",
@"Error indicating that an outgoing message had no valid recipients."));
[error setIsRetryable:NO];
return failureHandler(error);
}
2018-10-04 20:39:40 +02:00
// In the "self-send" special case, we ony need to send a sync message with a delivery receipt.
if ([thread isKindOfClass:[TSContactThread class]] &&
2018-12-21 20:15:19 +01:00
[((TSContactThread *)thread).contactIdentifier isEqualToString:self.tsAccountManager.localNumber]) {
2018-10-04 20:39:40 +02:00
// Send to self.
OWSAssertDebug(message.recipientIds.count == 1);
2018-12-21 20:15:19 +01:00
// Don't mark self-sent messages as read (or sent) until the sync transcript is sent.
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
successHandler();
2018-10-04 20:39:40 +02:00
return;
}
2018-10-04 21:22:42 +02:00
if (thread.isGroupThread) {
[self saveInfoMessageForGroupMessage:message inThread:thread];
}
2018-10-04 20:39:40 +02:00
NSError *error;
NSArray<NSString *> *_Nullable recipientIds = [self unsentRecipientsForMessage:message thread:thread error:&error];
if (error || !recipientIds) {
error = SSKEnsureError(
error, OWSErrorCodeMessageSendNoValidRecipients, @"Could not build recipients list for message.");
[error setIsRetryable:NO];
return failureHandler(error);
}
// Mark skipped recipients as such. We skip because:
//
// * Recipient is no longer in the group.
// * Recipient is blocked.
//
// Elsewhere, we skip recipient if their Signal account has been deactivated.
NSMutableSet<NSString *> *obsoleteRecipientIds = [NSMutableSet setWithArray:message.sendingRecipientIds];
[obsoleteRecipientIds minusSet:[NSSet setWithArray:recipientIds]];
if (obsoleteRecipientIds.count > 0) {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *recipientId in obsoleteRecipientIds) {
// Mark this recipient as "skipped".
[message updateWithSkippedRecipient:recipientId transaction:transaction];
}
}];
}
if (recipientIds.count < 1) {
// All recipients are already sent or can be skipped.
successHandler();
return;
}
NSArray<SignalRecipient *> *recipients = [self recipientsForRecipientIds:recipientIds];
BOOL isGroupSend = (thread && thread.isGroupThread);
NSMutableArray<NSError *> *sendErrors = [NSMutableArray array];
AnyPromise *sendPromise = [self sendPromiseForRecipients:recipients
message:message
thread:thread
senderCertificate:senderCertificate
sendErrors:sendErrors]
.then(^(id value) {
successHandler();
});
sendPromise.catch(^(id failure) {
NSError *firstRetryableError = nil;
NSError *firstNonRetryableError = nil;
2018-07-20 21:22:51 +02:00
NSArray<NSError *> *sendErrorsCopy;
@synchronized(sendErrors) {
sendErrorsCopy = [sendErrors copy];
}
for (NSError *error in sendErrorsCopy) {
// Some errors should be ignored when sending messages
// to groups. See discussion on
// NSError (OWSMessageSender) category.
2018-10-03 14:55:14 +02:00
if (isGroupSend && [error shouldBeIgnoredForGroups]) {
2018-07-20 21:22:51 +02:00
continue;
}
// Some errors should never be retried, in order to avoid
// hitting rate limits, for example. Unfortunately, since
// group send retry is all-or-nothing, we need to fail
// immediately even if some of the other recipients had
// retryable errors.
if ([error isFatal]) {
failureHandler(error);
return;
}
if ([error isRetryable] && !firstRetryableError) {
firstRetryableError = error;
} else if (![error isRetryable] && !firstNonRetryableError) {
firstNonRetryableError = error;
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
}
2018-10-03 14:55:14 +02:00
// If any of the send errors are retryable, we want to retry.
// Therefore, prefer to propagate a retryable error.
if (firstRetryableError) {
return failureHandler(firstRetryableError);
} else if (firstNonRetryableError) {
return failureHandler(firstNonRetryableError);
} else {
// If we only received errors that we should ignore,
2017-04-17 22:45:22 +02:00
// consider this send a success, unless the message could
// not be sent to any recipient.
2018-10-04 20:39:40 +02:00
if (message.sentRecipientsCount == 0) {
2017-04-17 22:45:22 +02:00
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageSendNoValidRecipients,
NSLocalizedString(@"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS",
@"Error indicating that an outgoing message had no valid recipients."));
[error setIsRetryable:NO];
failureHandler(error);
} else {
successHandler();
}
}
2018-07-20 21:22:51 +02:00
});
2018-10-04 20:39:40 +02:00
[sendPromise retainUntilComplete];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
- (void)unregisteredRecipient:(SignalRecipient *)recipient
message:(TSOutgoingMessage *)message
thread:(TSThread *)thread
{
2018-07-17 16:09:01 +02:00
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
2018-04-24 21:49:08 +02:00
if (thread.isGroupThread) {
// Mark as "skipped" group members who no longer have signal accounts.
[message updateWithSkippedRecipient:recipient.recipientId transaction:transaction];
}
2018-07-17 16:09:01 +02:00
if (![SignalRecipient isRegisteredRecipient:recipient.recipientId transaction:transaction]) {
2018-07-13 21:23:08 +02:00
return;
}
2018-07-16 17:25:10 +02:00
2018-11-01 21:06:03 +01:00
[SignalRecipient markRecipientAsUnregistered:recipient.recipientId transaction:transaction];
2018-07-13 21:23:08 +02:00
[[TSInfoMessage userNotRegisteredMessageInThread:thread recipientId:recipient.recipientId]
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
saveWithTransaction:transaction];
2018-07-17 16:09:01 +02:00
// TODO: Should we deleteAllSessionsForContact here?
// If so, we'll need to avoid doing a prekey fetch every
// time we try to send a message to an unregistered user.
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}];
}
- (nullable NSArray<NSDictionary *> *)deviceMessagesForMessageSend:(OWSMessageSend *)messageSend
error:(NSError **)errorHandle
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
2018-10-03 14:55:14 +02:00
OWSAssertDebug(messageSend);
OWSAssertDebug(errorHandle);
AssertIsOnSendingQueue();
2018-10-03 14:55:14 +02:00
SignalRecipient *recipient = messageSend.recipient;
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
NSArray<NSDictionary *> *deviceMessages;
@try {
deviceMessages = [self throws_deviceMessagesForMessageSend:messageSend];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
} @catch (NSException *exception) {
if ([exception.name isEqualToString:NoSessionForTransientMessageException]) {
// When users re-register, we don't want transient messages (like typing
// indicators) to cause users to hit the prekey fetch rate limit. So
// we silently discard these message if there is no pre-existing session
// for the recipient.
NSError *error = OWSErrorWithCodeDescription(
OWSErrorCodeNoSessionForTransientMessage, @"No session for transient message.");
[error setIsRetryable:NO];
[error setIsFatal:YES];
*errorHandle = error;
return nil;
} else if ([exception.name isEqualToString:UntrustedIdentityKeyException]) {
// This *can* happen under normal usage, but it should happen relatively rarely.
// We expect it to happen whenever Bob reinstalls, and Alice messages Bob before
// she can pull down his latest identity.
// If it's happening a lot, we should rethink our profile fetching strategy.
OWSProdInfo([OWSAnalyticsEvents messageSendErrorFailedDueToUntrustedKey]);
NSString *localizedErrorDescriptionFormat
= NSLocalizedString(@"FAILED_SENDING_BECAUSE_UNTRUSTED_IDENTITY_KEY",
@"action sheet header when re-sending message which failed because of untrusted identity keys");
NSString *localizedErrorDescription =
[NSString stringWithFormat:localizedErrorDescriptionFormat,
[self.contactsManager displayNameForPhoneIdentifier:recipient.recipientId]];
NSError *error = OWSErrorMakeUntrustedIdentityError(localizedErrorDescription, recipient.recipientId);
// Key will continue to be unaccepted, so no need to retry. It'll only cause us to hit the Pre-Key request
// rate limit
[error setIsRetryable:NO];
// Avoid the "Too many failures with this contact" error rate limiting.
[error setIsFatal:YES];
2018-10-03 14:55:14 +02:00
*errorHandle = error;
PreKeyBundle *_Nullable newKeyBundle = exception.userInfo[TSInvalidPreKeyBundleKey];
if (newKeyBundle == nil) {
OWSProdFail([OWSAnalyticsEvents messageSenderErrorMissingNewPreKeyBundle]);
2018-10-03 14:55:14 +02:00
return nil;
}
if (![newKeyBundle isKindOfClass:[PreKeyBundle class]]) {
OWSProdFail([OWSAnalyticsEvents messageSenderErrorUnexpectedKeyBundle]);
2018-10-03 14:55:14 +02:00
return nil;
}
NSData *newIdentityKeyWithVersion = newKeyBundle.identityKey;
if (![newIdentityKeyWithVersion isKindOfClass:[NSData class]]) {
OWSProdFail([OWSAnalyticsEvents messageSenderErrorInvalidIdentityKeyType]);
2018-10-03 14:55:14 +02:00
return nil;
}
// TODO migrate to storing the full 33 byte representation of the identity key.
if (newIdentityKeyWithVersion.length != kIdentityKeyLength) {
OWSProdFail([OWSAnalyticsEvents messageSenderErrorInvalidIdentityKeyLength]);
2018-10-03 14:55:14 +02:00
return nil;
}
NSData *newIdentityKey = [newIdentityKeyWithVersion throws_removeKeyType];
2018-10-03 23:04:41 +02:00
[self.identityManager saveRemoteIdentity:newIdentityKey recipientId:recipient.recipientId];
2018-10-03 14:55:14 +02:00
return nil;
}
if ([exception.name isEqualToString:OWSMessageSenderRateLimitedException]) {
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeSignalServiceRateLimited,
NSLocalizedString(@"FAILED_SENDING_BECAUSE_RATE_LIMIT",
@"action sheet header when re-sending message which failed because of too many attempts"));
// We're already rate-limited. No need to exacerbate the problem.
[error setIsRetryable:NO];
// Avoid exacerbating the rate limiting.
[error setIsFatal:YES];
2018-10-03 14:55:14 +02:00
*errorHandle = error;
return nil;
}
2018-10-03 14:55:14 +02:00
OWSLogWarn(@"Could not build device messages: %@", exception);
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
[error setIsRetryable:YES];
*errorHandle = error;
return nil;
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
2018-10-03 14:55:14 +02:00
return deviceMessages;
}
- (void)sendMessageToRecipient:(OWSMessageSend *)messageSend
{
OWSAssertDebug(messageSend);
OWSAssertDebug(messageSend.thread || [messageSend.message isKindOfClass:[OWSOutgoingSyncMessage class]]);
TSOutgoingMessage *message = messageSend.message;
SignalRecipient *recipient = messageSend.recipient;
OWSLogInfo(@"attempting to send message: %@, timestamp: %llu, recipient: %@",
message.class,
message.timestamp,
recipient.uniqueId);
AssertIsOnSendingQueue();
if ([TSPreKeyManager isAppLockedDueToPreKeyUpdateFailures]) {
OWSProdError([OWSAnalyticsEvents messageSendErrorFailedDueToPrekeyUpdateFailures]);
// Retry prekey update every time user tries to send a message while app
// is disabled due to prekey update failures.
//
// Only try to update the signed prekey; updating it is sufficient to
// re-enable message sending.
[TSPreKeyManager
rotateSignedPreKeyWithSuccess:^{
OWSLogInfo(@"New prekeys registered with server.");
NSError *error = OWSErrorMakeMessageSendDisabledDueToPreKeyUpdateFailuresError();
[error setIsRetryable:YES];
2018-10-04 20:39:40 +02:00
return messageSend.failure(error);
2018-10-03 14:55:14 +02:00
}
failure:^(NSError *error) {
OWSLogWarn(@"Failed to update prekeys with the server: %@", error);
2018-10-04 20:39:40 +02:00
return messageSend.failure(error);
2018-10-03 14:55:14 +02:00
}];
}
if (messageSend.remainingAttempts <= 0) {
// We should always fail with a specific error.
OWSProdFail([OWSAnalyticsEvents messageSenderErrorGenericSendFailure]);
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
[error setIsRetryable:YES];
2018-10-04 20:39:40 +02:00
return messageSend.failure(error);
2018-10-03 14:55:14 +02:00
}
2018-10-03 20:01:55 +02:00
2018-10-03 14:55:14 +02:00
// Consume an attempt.
messageSend.remainingAttempts = messageSend.remainingAttempts - 1;
2018-10-22 20:04:30 +02:00
// We need to disable UD for sync messages before we build the device messages,
// since we don't want to build a device message for the local device in the
// non-UD auth case.
if ([message isKindOfClass:[OWSOutgoingSyncMessage class]]
&& ![message isKindOfClass:[OWSOutgoingSentMessageTranscript class]]) {
[messageSend disableUD];
}
2018-10-03 14:55:14 +02:00
NSError *deviceMessagesError;
NSArray<NSDictionary *> *_Nullable deviceMessages =
[self deviceMessagesForMessageSend:messageSend error:&deviceMessagesError];
2018-10-03 14:55:14 +02:00
if (deviceMessagesError || !deviceMessages) {
OWSAssertDebug(deviceMessagesError);
2018-10-04 20:39:40 +02:00
return messageSend.failure(deviceMessagesError);
2018-10-03 14:55:14 +02:00
}
if (messageSend.isLocalNumber) {
OWSAssertDebug([message isKindOfClass:[OWSOutgoingSyncMessage class]]);
2018-03-02 05:31:41 +01:00
// Messages sent to the "local number" should be sync messages.
2017-11-07 20:52:31 +01:00
//
// We can skip sending sync messages if we know that we have no linked
// devices. However, we need to be sure to handle the case where the
// linked device list has just changed.
//
// The linked device list is reflected in two separate pieces of state:
//
// * OWSDevice's state is updated when you link or unlink a device.
// * SignalRecipient's state is updated by 409 "Mismatched devices"
// responses from the service.
//
// If _both_ of these pieces of state agree that there are no linked
// devices, then can safely skip sending sync message.
//
// NOTE: Sync messages sent via UD include the local device.
2017-11-07 20:52:31 +01:00
2017-11-10 18:04:24 +01:00
BOOL mayHaveLinkedDevices = [OWSDeviceManager.sharedManager mayHaveLinkedDevices:self.dbConnection];
2017-11-02 19:10:45 +01:00
BOOL hasDeviceMessages = NO;
for (NSDictionary<NSString *, id> *deviceMessage in deviceMessages) {
NSString *_Nullable destination = deviceMessage[@"destination"];
if (!destination) {
OWSFailDebug(@"Sync device message missing destination: %@", deviceMessage);
continue;
}
if (![destination isEqualToString:messageSend.localNumber]) {
OWSFailDebug(@"Sync device message has invalid destination: %@", deviceMessage);
continue;
}
NSNumber *_Nullable destinationDeviceId = deviceMessage[@"destinationDeviceId"];
if (!destinationDeviceId) {
OWSFailDebug(@"Sync device message missing destination device id: %@", deviceMessage);
continue;
}
if (destinationDeviceId.intValue != OWSDevicePrimaryDeviceId) {
hasDeviceMessages = YES;
break;
}
}
2017-11-02 19:10:45 +01:00
OWSLogInfo(@"mayHaveLinkedDevices: %d, hasDeviceMessages: %d", mayHaveLinkedDevices, hasDeviceMessages);
2018-03-02 05:31:41 +01:00
2017-11-10 17:04:40 +01:00
if (!mayHaveLinkedDevices && !hasDeviceMessages) {
OWSLogInfo(@"Ignoring sync message without secondary devices: %@", [message class]);
OWSAssertDebug([message isKindOfClass:[OWSOutgoingSyncMessage class]]);
dispatch_async([OWSDispatch sendingQueue], ^{
2018-10-03 14:55:14 +02:00
// This emulates the completion logic of an actual successful send (see below).
2018-04-27 22:37:09 +02:00
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
2018-10-03 14:55:14 +02:00
[message updateWithSkippedRecipient:messageSend.localNumber transaction:transaction];
2018-04-27 22:37:09 +02:00
}];
2018-10-04 20:39:40 +02:00
messageSend.success();
});
return;
2018-07-02 18:22:29 +02:00
} else if (mayHaveLinkedDevices && !hasDeviceMessages) {
2017-11-02 19:10:45 +01:00
// We may have just linked a new secondary device which is not yet reflected in
// the SignalRecipient that corresponds to ourself. Proceed. Client should learn
2017-11-07 20:52:31 +01:00
// of new secondary devices via 409 "Mismatched devices" response.
OWSLogWarn(@"account has secondary devices, but sync message has no device messages");
2018-07-02 18:22:29 +02:00
} else if (!mayHaveLinkedDevices && hasDeviceMessages) {
OWSFailDebug(@"sync message has device messages for unknown secondary devices.");
}
2018-07-17 16:09:01 +02:00
} else {
2018-10-31 19:24:13 +01:00
// This can happen for users who have unregistered.
// We still want to try sending to them in case they have re-registered.
2018-10-31 19:36:46 +01:00
if (deviceMessages.count < 1) {
OWSLogWarn(@"Message send attempt with no device messages.");
}
}
2018-11-29 20:30:16 +01:00
for (NSDictionary *deviceMessage in deviceMessages) {
NSNumber *_Nullable messageType = deviceMessage[@"type"];
OWSAssertDebug(messageType);
2018-11-29 21:00:37 +01:00
BOOL hasValidMessageType;
2018-11-29 20:30:16 +01:00
if (messageSend.isUDSend) {
2018-11-29 21:00:37 +01:00
hasValidMessageType = [messageType isEqualToNumber:@(TSUnidentifiedSenderMessageType)];
2018-11-29 20:30:16 +01:00
} else {
2019-05-10 05:38:00 +02:00
// Loki: TSFriendRequestMessageType represents a Loki friend request
NSArray *validMessageTypes = @[ @(TSEncryptedWhisperMessageType), @(TSPreKeyWhisperMessageType), @(TSFriendRequestMessageType) ];
2019-05-07 05:59:34 +02:00
hasValidMessageType = [validMessageTypes containsObject:messageType];
2019-05-16 07:32:43 +02:00
/* Loki: Original code
2018-11-29 21:00:37 +01:00
hasValidMessageType = ([messageType isEqualToNumber:@(TSEncryptedWhisperMessageType)] ||
2019-05-07 05:59:34 +02:00
[messageType isEqualToNumber:@(TSPreKeyWhisperMessageType)]);
*/
2018-11-29 20:30:16 +01:00
}
2018-11-29 21:00:37 +01:00
if (!hasValidMessageType) {
OWSFailDebug(@"Invalid message type: %@", messageType);
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
[error setIsRetryable:NO];
return messageSend.failure(error);
}
2018-11-29 20:30:16 +01:00
}
2018-03-02 05:31:41 +01:00
if (deviceMessages.count == 0) {
2018-03-02 16:53:22 +01:00
// This might happen:
//
// * The first (after upgrading?) time we send a sync message to our linked devices.
// * After unlinking all linked devices.
// * After trying and failing to link a device.
2018-07-13 20:42:11 +02:00
// * The first time we send a message to a user, if they don't have their
2018-07-17 16:37:26 +02:00
// default device. For example, if they have unregistered
2018-07-13 20:42:11 +02:00
// their primary but still have a linked device. Or later, when they re-register.
2018-03-02 16:53:22 +01:00
//
// When we're not sure if we have linked devices, we need to try
// to send self-sync messages even if they have no device messages
// so that we can learn from the service whether or not there are
// linked devices that we don't know about.
OWSLogWarn(@"Sending a message with no device messages.");
// Loki: We don't handle linked devices yet so it's better to just exit early
// This would only occur if we're sending a message to ourself
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
[error setIsRetryable:NO];
return messageSend.failure(error);
2018-03-02 05:31:41 +01:00
}
2019-05-15 08:48:16 +02:00
// Update the state to show that proof of work is being calculated
2019-05-07 03:56:00 +02:00
[self setIsCalculatingProofOfWorkForMessage:messageSend];
// Convert the message to a Loki message and send it using the Loki messaging API
NSDictionary *signalMessage = deviceMessages.firstObject;
2019-05-16 04:08:37 +02:00
// Update the thread's friend request status if needed
NSInteger *messageType = ((NSNumber *)signalMessage[@"type"]).integerValue;
if (messageType == TSFriendRequestMessageType) {
2019-05-20 04:31:21 +02:00
[message.thread saveFriendRequestStatus:TSThreadFriendRequestStatusRequestSending withTransaction:nil];
[message saveFriendRequestStatus:TSMessageFriendRequestStatusPending withTransaction:nil];
2019-05-16 04:08:37 +02:00
}
2019-05-07 05:53:31 +02:00
BOOL isPoWRequired = YES; // TODO: Base on message type
[[LokiAPI objc_sendSignalMessage:signalMessage to:recipient.recipientId timestamp:message.timestamp requiringPoW:isPoWRequired]
2019-05-17 04:38:36 +02:00
.thenOn(OWSDispatch.sendingQueue, ^(id result) {
// Loki
// ========
2019-05-15 08:48:16 +02:00
if (messageType == TSFriendRequestMessageType) {
2019-05-20 04:39:11 +02:00
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[message.thread saveFriendRequestStatus:TSThreadFriendRequestStatusRequestSent withTransaction:transaction];
2019-05-20 05:01:04 +02:00
[message.thread removeOutgoingFriendRequestMessagesWithTransaction:transaction];
// Set the expiration date
NSTimeInterval expirationInterval = 72 * kHourInterval;
NSDate *expireDate = [[NSDate new] dateByAddingTimeInterval:expirationInterval];
2019-05-20 04:39:11 +02:00
[message saveFriendRequestExpiresAt:[NSDate ows_millisecondsSince1970ForDate:expireDate] withTransaction:transaction];
}];
2019-05-15 08:48:16 +02:00
}
2019-05-17 04:38:36 +02:00
// ========
// Invoke the completion handler
[self messageSendDidSucceed:messageSend
deviceMessages:deviceMessages
wasSentByUD:false
wasSentByWebsocket:false];
})
2019-05-08 06:01:23 +02:00
.catchOn(OWSDispatch.sendingQueue, ^(NSError *error) {
// Loki
// ========
2019-05-16 04:08:37 +02:00
if (messageType == TSFriendRequestMessageType) {
2019-05-20 04:31:21 +02:00
[message.thread saveFriendRequestStatus:TSThreadFriendRequestStatusNone withTransaction:nil];
2019-05-16 04:08:37 +02:00
}
// ========
2019-05-16 04:08:37 +02:00
// Handle the error
NSUInteger statusCode = 0;
NSData *_Nullable responseData = nil;
if ([error.domain isEqualToString:TSNetworkManagerErrorDomain]) {
statusCode = error.code;
NSError *_Nullable underlyingError = error.userInfo[NSUnderlyingErrorKey];
if (underlyingError) {
2019-05-08 06:01:23 +02:00
responseData = underlyingError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
} else {
OWSFailDebug(@"Missing underlying error: %@", error);
}
} else {
OWSFailDebug(@"Unexpected error: %@", error);
}
[self messageSendDidFail:messageSend
deviceMessages:deviceMessages
statusCode:statusCode
error:error
responseData:responseData];
}) retainUntilComplete];
2019-05-07 03:56:00 +02:00
// Loki: Original code
/*
OWSRequestMaker *requestMaker = [[OWSRequestMaker alloc] initWithLabel:@"Message Send"
requestFactoryBlock:^(SMKUDAccessKey *_Nullable udAccessKey) {
return [OWSRequestFactory submitMessageRequestWithRecipient:recipient.recipientId
messages:deviceMessages
timeStamp:message.timestamp
udAccessKey:udAccessKey];
}
udAuthFailureBlock:^{
// Note the UD auth failure so subsequent retries
// to this recipient also use basic auth.
[messageSend setHasUDAuthFailed];
}
websocketFailureBlock:^{
// Note the websocket failure so subsequent retries
// to this recipient also use REST.
messageSend.hasWebsocketSendFailed = YES;
}
recipientId:recipient.recipientId
udAccess:messageSend.udAccess
canFailoverUDAuth:NO];
[[requestMaker makeRequestObjc]
.then(^(OWSRequestMakerResult *result) {
dispatch_async([OWSDispatch sendingQueue], ^{
[self messageSendDidSucceed:messageSend
deviceMessages:deviceMessages
wasSentByUD:result.wasSentByUD
wasSentByWebsocket:result.wasSentByWebsocket];
});
})
.catch(^(NSError *error) {
dispatch_async([OWSDispatch sendingQueue], ^{
NSUInteger statusCode = 0;
NSData *_Nullable responseData = nil;
if ([error.domain isEqualToString:@"SignalServiceKit.RequestMakerUDAuthError"]) {
// Try again.
OWSLogInfo(@"UD request auth failed; failing over to non-UD request.");
[error setIsRetryable:YES];
} else if ([error.domain isEqualToString:TSNetworkManagerErrorDomain]) {
statusCode = error.code;
NSError *_Nullable underlyingError = error.userInfo[NSUnderlyingErrorKey];
if (underlyingError) {
responseData
= underlyingError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
} else {
OWSFailDebug(@"Missing underlying error: %@", error);
}
2018-10-19 18:25:02 +02:00
} else {
2019-05-07 03:56:00 +02:00
OWSFailDebug(@"Unexpected error: %@", error);
}
2019-05-07 03:56:00 +02:00
[self messageSendDidFail:messageSend
deviceMessages:deviceMessages
statusCode:statusCode
error:error
responseData:responseData];
});
}) retainUntilComplete];
*/
2018-05-18 20:35:00 +02:00
}
2018-03-02 05:31:41 +01:00
2019-05-07 03:56:00 +02:00
- (void)setIsCalculatingProofOfWorkForMessage:(OWSMessageSend *)messageSend
{
OWSAssertDebug(messageSend);
2019-05-10 05:38:00 +02:00
dispatch_async(OWSDispatch.sendingQueue, ^{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[messageSend.message updateIsCalculatingProofOfWorkWithTransaction:transaction];
}];
});
}
2018-10-03 14:55:14 +02:00
- (void)messageSendDidSucceed:(OWSMessageSend *)messageSend
2018-05-18 20:35:00 +02:00
deviceMessages:(NSArray<NSDictionary *> *)deviceMessages
2018-11-14 14:30:25 +01:00
wasSentByUD:(BOOL)wasSentByUD
wasSentByWebsocket:(BOOL)wasSentByWebsocket
{
2018-10-03 14:55:14 +02:00
OWSAssertDebug(messageSend);
OWSAssertDebug(deviceMessages);
2018-05-18 20:35:00 +02:00
2018-10-03 14:55:14 +02:00
SignalRecipient *recipient = messageSend.recipient;
OWSLogInfo(@"successfully sent message: %@ timestamp: %llu, wasSentByUD: %d",
messageSend.message.class, messageSend.message.timestamp, wasSentByUD);
2018-05-18 20:35:00 +02:00
2018-10-03 14:55:14 +02:00
if (messageSend.isLocalNumber && deviceMessages.count == 0) {
OWSLogInfo(@"Sent a message with no device messages; clearing 'mayHaveLinkedDevices'.");
2018-05-18 20:35:00 +02:00
// In order to avoid skipping necessary sync messages, the default value
// for mayHaveLinkedDevices is YES. Once we've successfully sent a
// sync message with no device messages (e.g. the service has confirmed
// that we have no linked devices), we can set mayHaveLinkedDevices to NO
// to avoid unnecessary message sends for sync messages until we learn
// of a linked device (e.g. through the device linking UI or by receiving
// a sync message, etc.).
2018-11-13 22:35:06 +01:00
[OWSDeviceManager.sharedManager clearMayHaveLinkedDevices];
2018-05-18 20:35:00 +02:00
}
2018-04-23 16:30:51 +02:00
2018-05-18 20:35:00 +02:00
dispatch_async([OWSDispatch sendingQueue], ^{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[messageSend.message updateWithSentRecipient:messageSend.recipient.uniqueId
wasSentByUD:wasSentByUD
transaction:transaction];
2018-07-13 21:23:08 +02:00
2018-07-16 17:25:10 +02:00
// If we've just delivered a message to a user, we know they
// have a valid Signal account.
2018-07-17 16:09:01 +02:00
[SignalRecipient markRecipientAsRegisteredAndGet:recipient.recipientId transaction:transaction];
2018-05-18 20:35:00 +02:00
}];
2018-05-18 19:55:22 +02:00
2018-10-04 20:39:40 +02:00
messageSend.success();
2018-05-18 20:35:00 +02:00
});
}
2018-05-18 19:55:22 +02:00
2018-10-03 14:55:14 +02:00
- (void)messageSendDidFail:(OWSMessageSend *)messageSend
2018-05-18 20:35:00 +02:00
deviceMessages:(NSArray<NSDictionary *> *)deviceMessages
statusCode:(NSInteger)statusCode
error:(NSError *)responseError
responseData:(nullable NSData *)responseData
2018-05-18 20:35:00 +02:00
{
2018-10-03 14:55:14 +02:00
OWSAssertDebug(messageSend);
OWSAssertDebug(messageSend.thread || [messageSend.message isKindOfClass:[OWSOutgoingSyncMessage class]]);
OWSAssertDebug(deviceMessages);
OWSAssertDebug(responseError);
2018-10-03 14:55:14 +02:00
TSOutgoingMessage *message = messageSend.message;
SignalRecipient *recipient = messageSend.recipient;
2018-10-23 16:54:33 +02:00
OWSLogInfo(@"failed to send message: %@, timestamp: %llu, to recipient: %@",
message.class,
message.timestamp,
recipient.uniqueId);
2018-05-18 19:55:22 +02:00
2018-05-18 20:35:00 +02:00
void (^retrySend)(void) = ^void() {
2018-10-03 14:55:14 +02:00
if (messageSend.remainingAttempts <= 0) {
2018-10-04 20:39:40 +02:00
return messageSend.failure(responseError);
2018-05-18 20:35:00 +02:00
}
2017-05-02 17:31:29 +02:00
2018-05-18 20:35:00 +02:00
dispatch_async([OWSDispatch sendingQueue], ^{
OWSLogDebug(@"Retrying: %@", message.debugDescription);
2018-10-04 20:39:40 +02:00
[self sendMessageToRecipient:messageSend];
2018-05-18 20:35:00 +02:00
});
};
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
2018-07-17 16:09:01 +02:00
void (^handle404)(void) = ^{
OWSLogWarn(@"Unregistered recipient: %@", recipient.uniqueId);
2018-07-17 16:09:01 +02:00
dispatch_async([OWSDispatch sendingQueue], ^{
2019-01-08 21:10:32 +01:00
if (![messageSend.message isKindOfClass:[OWSOutgoingSyncMessage class]]) {
TSThread *_Nullable thread = messageSend.thread;
OWSAssertDebug(thread);
[self unregisteredRecipient:recipient message:message thread:thread];
}
2018-07-17 16:09:01 +02:00
NSError *error = OWSErrorMakeNoSuchSignalRecipientError();
// No need to retry if the recipient is not registered.
[error setIsRetryable:NO];
// If one member of a group deletes their account,
// the group should ignore errors when trying to send
// messages to this ex-member.
[error setShouldBeIgnoredForGroups:YES];
2018-10-04 20:39:40 +02:00
messageSend.failure(error);
2018-07-17 16:09:01 +02:00
});
};
2018-05-18 20:35:00 +02:00
switch (statusCode) {
case 401: {
OWSLogWarn(@"Unable to send due to invalid credentials. Did the user's client get de-authed by "
@"registering elsewhere?");
2018-05-18 20:35:00 +02:00
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeSignalServiceFailure,
NSLocalizedString(
@"ERROR_DESCRIPTION_SENDING_UNAUTHORIZED", @"Error message when attempting to send message"));
// No need to retry if we've been de-authed.
[error setIsRetryable:NO];
2018-10-04 20:39:40 +02:00
return messageSend.failure(error);
2018-05-18 20:35:00 +02:00
}
case 404: {
2018-07-17 16:09:01 +02:00
handle404();
return;
2018-05-18 20:35:00 +02:00
}
case 409: {
// Mismatched devices
OWSLogWarn(@"Mismatched devices for recipient: %@ (%zd)", recipient.uniqueId, deviceMessages.count);
2018-05-18 19:55:22 +02:00
NSError *_Nullable error = nil;
NSDictionary *_Nullable responseJson = nil;
if (responseData) {
responseJson = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
}
if (error || !responseJson) {
2018-05-18 20:35:00 +02:00
OWSProdError([OWSAnalyticsEvents messageSenderErrorCouldNotParseMismatchedDevicesJson]);
[error setIsRetryable:YES];
2018-10-04 20:39:40 +02:00
return messageSend.failure(error);
2018-05-18 20:35:00 +02:00
}
2018-07-17 16:09:01 +02:00
NSNumber *_Nullable errorCode = responseJson[@"code"];
if ([@(404) isEqual:errorCode]) {
// Some 404s are returned as 409.
handle404();
return;
}
[self handleMismatchedDevicesWithResponseJson:responseJson recipient:recipient completion:retrySend];
2018-10-23 16:31:29 +02:00
if (messageSend.isLocalNumber) {
// Don't use websocket; it may have obsolete cached state.
[messageSend setHasWebsocketSendFailed:YES];
}
2018-05-18 20:35:00 +02:00
break;
}
case 410: {
// Stale devices
OWSLogWarn(@"Stale devices for recipient: %@", recipient.uniqueId);
2018-05-18 20:35:00 +02:00
NSError *_Nullable error = nil;
NSDictionary *_Nullable responseJson = nil;
if (responseData) {
responseJson = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
}
if (error || !responseJson) {
OWSLogWarn(@"Stale devices but server didn't specify devices in response.");
2018-05-18 20:35:00 +02:00
NSError *error = OWSErrorMakeUnableToProcessServerResponseError();
[error setIsRetryable:YES];
2018-10-04 20:39:40 +02:00
return messageSend.failure(error);
2018-05-18 20:35:00 +02:00
}
[self handleStaleDevicesWithResponseJson:responseJson recipientId:recipient.uniqueId completion:retrySend];
2018-10-23 16:31:29 +02:00
if (messageSend.isLocalNumber) {
// Don't use websocket; it may have obsolete cached state.
[messageSend setHasWebsocketSendFailed:YES];
}
2018-05-18 20:35:00 +02:00
break;
}
default:
retrySend();
break;
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
- (void)handleMismatchedDevicesWithResponseJson:(NSDictionary *)responseJson
recipient:(SignalRecipient *)recipient
completion:(void (^)(void))completionHandler
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
OWSAssertDebug(responseJson);
OWSAssertDebug(recipient);
OWSAssertDebug(completionHandler);
NSArray *extraDevices = responseJson[@"extraDevices"];
NSArray *missingDevices = responseJson[@"missingDevices"];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
2017-11-10 17:04:40 +01:00
if (missingDevices.count > 0) {
2018-12-21 20:15:19 +01:00
NSString *localNumber = self.tsAccountManager.localNumber;
2017-11-10 17:04:40 +01:00
if ([localNumber isEqualToString:recipient.uniqueId]) {
2018-01-11 16:25:13 +01:00
[OWSDeviceManager.sharedManager setMayHaveLinkedDevices];
2017-11-10 17:04:40 +01:00
}
}
2018-02-02 18:32:23 +01:00
[self.dbConnection
2018-07-25 17:42:50 +02:00
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
2018-01-30 21:05:04 +01:00
if (extraDevices.count < 1 && missingDevices.count < 1) {
OWSProdFail([OWSAnalyticsEvents messageSenderErrorNoMissingOrExtraDevices]);
}
2017-05-02 17:31:29 +02:00
2018-10-18 17:20:47 +02:00
[recipient updateRegisteredRecipientWithDevicesToAdd:missingDevices
devicesToRemove:extraDevices
transaction:transaction];
2018-10-18 16:06:03 +02:00
2018-01-30 21:05:04 +01:00
if (extraDevices && extraDevices.count > 0) {
2018-10-18 17:20:47 +02:00
OWSLogInfo(@"Deleting sessions for extra devices: %@", extraDevices);
2018-01-30 21:05:04 +01:00
for (NSNumber *extraDeviceId in extraDevices) {
[self.primaryStorage deleteSessionForContact:recipient.uniqueId
2018-01-30 21:05:04 +01:00
deviceId:extraDeviceId.intValue
protocolContext:transaction];
}
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
2018-01-30 21:05:04 +01:00
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
completionHandler();
});
}];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
- (void)handleMessageSentLocally:(TSOutgoingMessage *)message
2018-12-21 20:15:19 +01:00
success:(void (^)(void))successParam
2018-10-24 16:53:23 +02:00
failure:(RetryableFailureHandler)failure
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
2018-12-21 20:15:19 +01:00
dispatch_block_t success = ^{
TSThread *_Nullable thread = message.thread;
if (thread && [thread isKindOfClass:[TSContactThread class]] &&
[thread.contactIdentifier isEqualToString:self.tsAccountManager.localNumber]) {
OWSAssertDebug(message.recipientIds.count == 1);
// Don't mark self-sent messages as read (or sent) until the sync transcript is sent.
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *recipientId in message.sendingRecipientIds) {
[message updateWithReadRecipientId:recipientId
readTimestamp:message.timestamp
transaction:transaction];
}
}];
}
successParam();
};
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[[OWSDisappearingMessagesJob sharedJob] startAnyExpirationForMessage:message
expirationStartedAt:[NSDate ows_millisecondTimeStamp]
transaction:transaction];
}];
2018-10-24 16:53:23 +02:00
if (!message.shouldSyncTranscript) {
return success();
}
2019-02-21 21:21:00 +01:00
BOOL shouldSendTranscript = (AreRecipientUpdatesEnabled() || !message.hasSyncedTranscript);
if (!shouldSendTranscript) {
return success();
}
2019-02-21 21:21:00 +01:00
BOOL isRecipientUpdate = message.hasSyncedTranscript;
[self
sendSyncTranscriptForMessage:message
isRecipientUpdate:isRecipientUpdate
success:^{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[message updateWithHasSyncedTranscript:YES transaction:transaction];
}];
success();
}
failure:failure];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
- (void)sendSyncTranscriptForMessage:(TSOutgoingMessage *)message
isRecipientUpdate:(BOOL)isRecipientUpdate
2018-10-24 16:53:23 +02:00
success:(void (^)(void))success
failure:(RetryableFailureHandler)failure
{
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
OWSOutgoingSentMessageTranscript *sentMessageTranscript =
[[OWSOutgoingSentMessageTranscript alloc] initWithOutgoingMessage:message isRecipientUpdate:isRecipientUpdate];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
2018-10-03 14:55:14 +02:00
NSString *recipientId = self.tsAccountManager.localNumber;
2018-09-17 15:27:58 +02:00
__block SignalRecipient *recipient;
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
2018-09-17 15:51:44 +02:00
recipient = [SignalRecipient markRecipientAsRegisteredAndGet:recipientId transaction:transaction];
2018-09-17 15:27:58 +02:00
}];
2018-10-03 14:55:14 +02:00
OWSMessageSend *messageSend = [[OWSMessageSend alloc] initWithMessage:sentMessageTranscript
2018-10-04 20:39:40 +02:00
thread:message.thread
recipient:recipient
2018-11-07 20:16:51 +01:00
senderCertificate:nil
udAccess:nil
2018-10-04 20:39:40 +02:00
localNumber:self.tsAccountManager.localNumber
success:^{
OWSLogInfo(@"Successfully sent sync transcript.");
2018-10-24 16:53:23 +02:00
success();
}
failure:^(NSError *error) {
OWSLogInfo(@"Failed to send sync transcript: %@ (isRetryable: %d)", error, [error isRetryable]);
2018-10-24 16:53:23 +02:00
failure(error);
}];
2018-10-04 20:39:40 +02:00
[self sendMessageToRecipient:messageSend];
}
- (NSArray<NSDictionary *> *)throws_deviceMessagesForMessageSend:(OWSMessageSend *)messageSend
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
2018-10-03 14:55:14 +02:00
OWSAssertDebug(messageSend.message);
OWSAssertDebug(messageSend.recipient);
SignalRecipient *recipient = messageSend.recipient;
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
NSMutableArray *messagesArray = [NSMutableArray arrayWithCapacity:recipient.devices.count];
2018-10-03 14:55:14 +02:00
NSData *_Nullable plainText = [messageSend.message buildPlainTextData:messageSend.recipient];
if (!plainText) {
OWSRaiseException(InvalidMessageException, @"Failed to build message proto");
}
2018-10-03 14:55:14 +02:00
OWSLogDebug(
@"built message: %@ plainTextData.length: %lu", [messageSend.message class], (unsigned long)plainText.length);
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
2018-10-22 19:57:21 +02:00
OWSLogVerbose(@"building device messages for: %@ %@ (isLocalNumber: %d, isUDSend: %d)",
recipient.recipientId,
recipient.devices,
messageSend.isLocalNumber,
messageSend.isUDSend);
// Loki: Since we don't support multi-device sending yet, just send it to the primary device
NSMutableArray<NSNumber *> *deviceIds = [NSMutableArray arrayWithObject:@(OWSDevicePrimaryDeviceId)];
2019-05-16 07:32:43 +02:00
/* Loki: Original code
NSMutableArray<NSNumber *> *deviceIds = [recipient.devices mutableCopy];
*/
2019-05-07 03:56:00 +02:00
OWSAssertDebug(deviceIds);
if (messageSend.isLocalNumber) {
[deviceIds removeObject:@(OWSDevicePrimaryDeviceId)];
}
for (NSNumber *deviceId in deviceIds) {
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
@try {
2018-10-18 19:50:48 +02:00
// This may involve blocking network requests, so we do it _before_
// we open a transaction.
2019-05-10 05:38:00 +02:00
// A friend request means we don't have a session with the person
// There's no point to check for it
2019-05-10 05:38:00 +02:00
Boolean isFriendRequest = [messageSend.message isKindOfClass:OWSFriendRequestMessage.class];
2019-05-07 05:59:34 +02:00
if (!isFriendRequest) {
[self throws_ensureRecipientHasSessionForMessageSend:messageSend deviceId:deviceId];
}
2018-10-18 16:06:03 +02:00
2018-11-29 23:30:07 +01:00
__block NSDictionary *_Nullable messageDict;
__block NSException *encryptionException;
2018-02-02 18:32:23 +01:00
[self.dbConnection
2018-01-30 21:05:04 +01:00
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
@try {
messageDict = [self throws_encryptedMessageForMessageSend:messageSend
deviceId:deviceId
plainText:plainText
transaction:transaction];
2018-01-30 21:05:04 +01:00
} @catch (NSException *exception) {
encryptionException = exception;
}
}];
if (encryptionException) {
OWSLogInfo(@"Exception during encryption: %@", encryptionException);
@throw encryptionException;
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
if (messageDict) {
[messagesArray addObject:messageDict];
} else {
2018-01-25 16:44:13 +01:00
OWSRaiseException(InvalidMessageException, @"Failed to encrypt message");
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
} @catch (NSException *exception) {
if ([exception.name isEqualToString:OWSMessageSenderInvalidDeviceException]) {
2018-07-25 17:42:50 +02:00
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
2018-10-18 17:20:47 +02:00
[recipient updateRegisteredRecipientWithDevicesToAdd:nil
devicesToRemove:@[ deviceId ]
transaction:transaction];
2018-07-25 17:42:50 +02:00
}];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
} else {
@throw exception;
}
}
}
return [messagesArray copy];
}
- (void)throws_ensureRecipientHasSessionForMessageSend:(OWSMessageSend *)messageSend deviceId:(NSNumber *)deviceId
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
2018-10-03 20:01:55 +02:00
OWSAssertDebug(messageSend);
2018-10-03 23:04:41 +02:00
OWSAssertDebug(deviceId);
2018-10-03 20:01:55 +02:00
OWSPrimaryStorage *storage = self.primaryStorage;
SignalRecipient *recipient = messageSend.recipient;
NSString *recipientId = recipient.recipientId;
OWSAssertDebug(recipientId.length > 0);
2018-07-17 16:09:01 +02:00
2018-10-18 16:06:03 +02:00
__block BOOL hasSession;
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
hasSession = [storage containsSession:recipientId deviceId:[deviceId intValue] protocolContext:transaction];
}];
if (hasSession) {
return;
}
// Discard "typing indicator" messages if there is no existing session with the user.
BOOL canSafelyBeDiscarded = messageSend.message.isOnline;
if (canSafelyBeDiscarded) {
OWSRaiseException(NoSessionForTransientMessageException, @"No session for transient message.");
}
PreKeyBundle *_Nullable bundle = [storage getPreKeyBundleForContact:recipientId];
__block NSException *_Nullable exception;
2018-10-03 20:01:55 +02:00
/** Loki: Original code
* ================
2018-10-18 16:06:03 +02:00
__block dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block PreKeyBundle *_Nullable bundle;
__block NSException *_Nullable exception;
[self makePrekeyRequestForMessageSend:messageSend
deviceId:deviceId
success:^(PreKeyBundle *_Nullable responseBundle) {
bundle = responseBundle;
dispatch_semaphore_signal(sema);
}
2018-10-18 16:06:03 +02:00
failure:^(NSUInteger statusCode) {
if (statusCode == 404) {
// Can't throw exception from within callback as it's probabably a different thread.
exception = [NSException exceptionWithName:OWSMessageSenderInvalidDeviceException
reason:@"Device not registered"
userInfo:nil];
} else if (statusCode == 413) {
// Can't throw exception from within callback as it's probabably a different thread.
exception = [NSException exceptionWithName:OWSMessageSenderRateLimitedException
reason:@"Too many prekey requests"
userInfo:nil];
}
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
if (exception) {
@throw exception;
}
* ================
*/
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
2018-10-18 16:06:03 +02:00
if (!bundle) {
NSString *missingPrekeyBundleException = @"missingPrekeyBundleException";
OWSRaiseException(
missingPrekeyBundleException, @"Can't get a prekey bundle from the server with required information");
} else {
SessionBuilder *builder = [[SessionBuilder alloc] initWithSessionStore:storage
preKeyStore:storage
signedPreKeyStore:storage
identityKeyStore:self.identityManager
recipientId:recipientId
deviceId:[deviceId intValue]];
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
@try {
[builder throws_processPrekeyBundle:bundle protocolContext:transaction];
// Loki: Discard the prekey bundle here since the session is initiated
[storage removePreKeyBundleForContact:recipientId transaction:transaction];
2018-10-18 16:06:03 +02:00
} @catch (NSException *caughtException) {
exception = caughtException;
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
2018-10-18 16:06:03 +02:00
}];
if (exception) {
if ([exception.name isEqualToString:UntrustedIdentityKeyException]) {
OWSRaiseExceptionWithUserInfo(UntrustedIdentityKeyException,
(@{ TSInvalidPreKeyBundleKey : bundle, TSInvalidRecipientKey : recipientId }),
@"");
}
@throw exception;
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
}
2018-10-03 23:04:41 +02:00
}
2018-10-18 18:12:13 +02:00
2018-10-18 16:06:03 +02:00
- (void)makePrekeyRequestForMessageSend:(OWSMessageSend *)messageSend
deviceId:(NSNumber *)deviceId
success:(void (^)(PreKeyBundle *_Nullable))success
failure:(void (^)(NSUInteger))failure {
OWSAssertDebug(messageSend);
OWSAssertDebug(deviceId);
SignalRecipient *recipient = messageSend.recipient;
NSString *recipientId = recipient.recipientId;
OWSAssertDebug(recipientId.length > 0);
2018-10-26 20:07:17 +02:00
OWSRequestMaker *requestMaker = [[OWSRequestMaker alloc] initWithLabel:@"Prekey Fetch"
requestFactoryBlock:^(SMKUDAccessKey *_Nullable udAccessKey) {
2018-10-19 18:25:02 +02:00
return [OWSRequestFactory recipientPrekeyRequestWithRecipient:recipientId
deviceId:[deviceId stringValue]
2018-10-25 20:45:12 +02:00
udAccessKey:udAccessKey];
2018-10-19 18:25:02 +02:00
}
udAuthFailureBlock:^{
2018-10-30 21:15:27 +01:00
// Note the UD auth failure so subsequent retries
// to this recipient also use basic auth.
2018-10-19 18:25:02 +02:00
[messageSend setHasUDAuthFailed];
}
websocketFailureBlock:^{
2018-10-30 21:15:27 +01:00
// Note the websocket failure so subsequent retries
// to this recipient also use REST.
2018-10-19 18:25:02 +02:00
messageSend.hasWebsocketSendFailed = YES;
}
recipientId:recipientId
2018-10-30 17:06:20 +01:00
udAccess:messageSend.udAccess
canFailoverUDAuth:YES];
2018-10-19 18:25:02 +02:00
[[requestMaker makeRequestObjc]
.then(^(OWSRequestMakerResult *result) {
// We _do not_ want to dispatch to the sendingQueue here; we're
// using a semaphore on the sendingQueue to block on this request.
const id responseObject = result.responseObject;
PreKeyBundle *_Nullable bundle =
[PreKeyBundle preKeyBundleFromDictionary:responseObject forDeviceNumber:deviceId];
success(bundle);
})
.catch(^(NSError *error) {
// We _do not_ want to dispatch to the sendingQueue here; we're
// using a semaphore on the sendingQueue to block on this request.
NSUInteger statusCode = 0;
if ([error.domain isEqualToString:TSNetworkManagerErrorDomain]) {
statusCode = error.code;
} else {
OWSFailDebug(@"Unexpected error: %@", error);
}
2018-10-18 16:06:03 +02:00
2018-10-19 18:25:02 +02:00
failure(statusCode);
}) retainUntilComplete];
2018-10-18 16:06:03 +02:00
}
2018-10-03 23:04:41 +02:00
2019-05-07 05:59:34 +02:00
- (nullable NSDictionary *)throws_encryptedFriendMessageForMessageSend:(OWSMessageSend *)messageSend
deviceId:(NSNumber *)deviceId
plainText:(NSData *)plainText
{
OWSAssertDebug(messageSend);
OWSAssertDebug(deviceId);
OWSAssertDebug(plainText);
SignalRecipient *recipient = messageSend.recipient;
NSString *recipientId = recipient.recipientId;
2019-05-10 05:38:00 +02:00
FallBackSessionCipher *cipher = [[FallBackSessionCipher alloc] initWithRecipientId:recipientId identityKeyStore:self.identityManager];
2019-05-07 05:59:34 +02:00
// This will return nil if encryption failed
NSData *_Nullable serializedMessage = [cipher encryptWithMessage:[plainText paddedMessageBody]];
if (!serializedMessage) {
OWSFailDebug(@"Failed to encrypt friend message to: %@", recipientId);
return nil;
}
OWSMessageServiceParams *messageParams =
[[OWSMessageServiceParams alloc] initWithType:TSFriendRequestMessageType
recipientId:recipientId
device:[deviceId intValue]
content:serializedMessage
isSilent:false
isOnline:false
registrationId:0];
NSError *error;
NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:messageParams error:&error];
if (error) {
OWSProdError([OWSAnalyticsEvents messageSendErrorCouldNotSerializeMessageJson]);
return nil;
}
return jsonDict;
}
2018-11-29 23:30:07 +01:00
- (nullable NSDictionary *)throws_encryptedMessageForMessageSend:(OWSMessageSend *)messageSend
deviceId:(NSNumber *)deviceId
plainText:(NSData *)plainText
transaction:(YapDatabaseReadWriteTransaction *)transaction
2018-10-03 23:04:41 +02:00
{
OWSAssertDebug(messageSend);
OWSAssertDebug(deviceId);
OWSAssertDebug(plainText);
OWSAssertDebug(transaction);
OWSPrimaryStorage *storage = self.primaryStorage;
TSOutgoingMessage *message = messageSend.message;
SignalRecipient *recipient = messageSend.recipient;
NSString *recipientId = recipient.recipientId;
OWSAssertDebug(recipientId.length > 0);
2019-05-07 05:59:34 +02:00
// Loki: Handle friend requests differently
2019-05-10 05:38:00 +02:00
Boolean isFriendRequest = [messageSend.message isKindOfClass:OWSFriendRequestMessage.class];
2019-05-07 05:59:34 +02:00
if (isFriendRequest) {
return [self throws_encryptedFriendMessageForMessageSend:messageSend deviceId:deviceId plainText:plainText];
}
2018-10-03 23:04:41 +02:00
// This may throw an exception.
2018-10-18 16:06:03 +02:00
if (![storage containsSession:recipientId deviceId:[deviceId intValue] protocolContext:transaction]) {
NSString *missingSessionException = @"missingSessionException";
OWSRaiseException(missingSessionException,
@"Unexpectedly missing session for recipient: %@, device: %@",
recipientId,
deviceId);
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
SessionCipher *cipher = [[SessionCipher alloc] initWithSessionStore:storage
preKeyStore:storage
signedPreKeyStore:storage
2018-10-03 23:04:41 +02:00
identityKeyStore:self.identityManager
2018-10-03 20:01:55 +02:00
recipientId:recipientId
2018-10-03 23:04:41 +02:00
deviceId:[deviceId intValue]];
2018-10-03 23:04:41 +02:00
NSData *_Nullable serializedMessage;
TSWhisperMessageType messageType;
2018-10-05 16:41:10 +02:00
if (messageSend.isUDSend) {
2018-10-03 23:04:41 +02:00
NSError *error;
SMKSecretSessionCipher *_Nullable secretCipher =
[[SMKSecretSessionCipher alloc] initWithSessionStore:self.primaryStorage
preKeyStore:self.primaryStorage
signedPreKeyStore:self.primaryStorage
identityStore:self.identityManager
error:&error];
if (error || !secretCipher) {
OWSRaiseException(@"SecretSessionCipherFailure", @"Can't create secret session cipher.");
}
serializedMessage = [secretCipher throwswrapped_encryptMessageWithRecipientId:recipientId
deviceId:deviceId.intValue
paddedPlaintext:[plainText paddedMessageBody]
senderCertificate:messageSend.senderCertificate
protocolContext:transaction
error:&error];
2018-10-28 19:18:04 +01:00
SCKRaiseIfExceptionWrapperError(error);
2018-11-29 23:30:07 +01:00
if (!serializedMessage || error) {
OWSFailDebug(@"error while UD encrypting message: %@", error);
return nil;
}
2018-10-03 23:04:41 +02:00
messageType = TSUnidentifiedSenderMessageType;
} else {
2018-10-05 16:32:32 +02:00
// This may throw an exception.
2018-10-03 23:04:41 +02:00
id<CipherMessage> encryptedMessage =
[cipher throws_encryptMessage:[plainText paddedMessageBody] protocolContext:transaction];
2018-10-03 23:04:41 +02:00
serializedMessage = encryptedMessage.serialized;
messageType = [self messageTypeForCipherMessage:encryptedMessage];
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
2018-10-03 20:01:55 +02:00
BOOL isSilent = message.isSilent;
BOOL isOnline = message.isOnline;
2018-01-30 21:05:04 +01:00
OWSMessageServiceParams *messageParams =
[[OWSMessageServiceParams alloc] initWithType:messageType
2018-10-03 20:01:55 +02:00
recipientId:recipientId
2018-10-03 23:04:41 +02:00
device:[deviceId intValue]
2018-01-30 21:05:04 +01:00
content:serializedMessage
isSilent:isSilent
isOnline:isOnline
registrationId:[cipher throws_remoteRegistrationId:transaction]];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
NSError *error;
NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:messageParams error:&error];
if (error) {
OWSProdError([OWSAnalyticsEvents messageSendErrorCouldNotSerializeMessageJson]);
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
return nil;
}
return jsonDict;
}
- (TSWhisperMessageType)messageTypeForCipherMessage:(id<CipherMessage>)cipherMessage
{
2018-09-28 16:56:53 +02:00
switch (cipherMessage.cipherMessageType) {
case CipherMessageType_Whisper:
return TSEncryptedWhisperMessageType;
case CipherMessageType_Prekey:
return TSPreKeyWhisperMessageType;
default:
return TSUnknownMessageType;
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
}
2018-10-05 16:32:32 +02:00
- (void)saveInfoMessageForGroupMessage:(TSOutgoingMessage *)message inThread:(TSThread *)thread
{
2018-10-04 21:22:42 +02:00
OWSAssertDebug(message);
OWSAssertDebug(thread);
if (message.groupMetaMessage == TSGroupMetaMessageDeliver) {
// TODO: Why is this necessary?
[message save];
} else if (message.groupMetaMessage == TSGroupMetaMessageQuit) {
// MJK TODO - remove senderTimestamp
2018-10-05 16:32:32 +02:00
[[[TSInfoMessage alloc] initWithTimestamp:message.timestamp
inThread:thread
messageType:TSInfoMessageTypeGroupQuit
customMessage:message.customMessage] save];
} else {
// MJK TODO - remove senderTimestamp
2018-10-05 16:32:32 +02:00
[[[TSInfoMessage alloc] initWithTimestamp:message.timestamp
inThread:thread
messageType:TSInfoMessageTypeGroupUpdate
customMessage:message.customMessage] save];
}
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
// Called when the server indicates that the devices no longer exist - e.g. when the remote recipient has reinstalled.
- (void)handleStaleDevicesWithResponseJson:(NSDictionary *)responseJson
recipientId:(NSString *)identifier
completion:(void (^)(void))completionHandler
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
dispatch_async([OWSDispatch sendingQueue], ^{
NSArray *devices = responseJson[@"staleDevices"];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
if (!([devices count] > 0)) {
return;
}
2018-02-02 18:32:23 +01:00
[self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSUInteger i = 0; i < [devices count]; i++) {
int deviceNumber = [devices[i] intValue];
[[OWSPrimaryStorage sharedManager] deleteSessionForContact:identifier
deviceId:deviceNumber
protocolContext:transaction];
}
2018-01-30 21:05:04 +01:00
}];
completionHandler();
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
});
}
@end
@implementation OutgoingMessagePreparer
#pragma mark - Dependencies
+ (YapDatabaseConnection *)dbConnection
{
return SSKEnvironment.shared.primaryStorage.dbReadWriteConnection;
}
#pragma mark -
2019-01-17 17:56:21 +01:00
+ (NSArray<NSString *> *)prepareMessageForSending:(TSOutgoingMessage *)message
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
2019-01-17 17:56:21 +01:00
OWSAssertDebug(message);
OWSAssertDebug(transaction);
NSMutableArray<NSString *> *attachmentIds = [NSMutableArray new];
if (message.attachmentIds) {
[attachmentIds addObjectsFromArray:message.attachmentIds];
}
if (message.quotedMessage) {
2019-01-17 17:56:21 +01:00
// Though we currently only ever expect at most one thumbnail, the proto data model
// suggests this could change. The logic is intended to work with multiple, but
// if we ever actually want to send multiple, we should do more testing.
NSArray<TSAttachmentStream *> *quotedThumbnailAttachments =
[message.quotedMessage createThumbnailAttachmentsIfNecessaryWithTransaction:transaction];
2019-01-17 17:56:21 +01:00
for (TSAttachmentStream *attachment in quotedThumbnailAttachments) {
[attachmentIds addObject:attachment.uniqueId];
}
}
if (message.contactShare.avatarAttachmentId != nil) {
2019-01-17 17:56:21 +01:00
TSAttachment *attachment = [message.contactShare avatarAttachmentWithTransaction:transaction];
if ([attachment isKindOfClass:[TSAttachmentStream class]]) {
[attachmentIds addObject:attachment.uniqueId];
} else {
OWSFailDebug(@"unexpected avatarAttachment: %@", attachment);
}
}
if (message.linkPreview.imageAttachmentId != nil) {
TSAttachment *attachment =
[TSAttachment fetchObjectWithUniqueID:message.linkPreview.imageAttachmentId transaction:transaction];
if ([attachment isKindOfClass:[TSAttachmentStream class]]) {
[attachmentIds addObject:attachment.uniqueId];
} else {
2019-01-17 17:56:21 +01:00
OWSFailDebug(@"unexpected attachment: %@", attachment);
}
}
// All outgoing messages should be saved at the time they are enqueued.
[message saveWithTransaction:transaction];
// When we start a message send, all "failed" recipients should be marked as "sending".
[message updateWithMarkingAllUnsentRecipientsAsSendingWithTransaction:transaction];
2019-01-17 17:56:21 +01:00
return attachmentIds;
}
2018-11-02 16:33:05 +01:00
+ (void)prepareAttachments:(NSArray<OWSOutgoingAttachmentInfo *> *)attachmentInfos
inMessage:(TSOutgoingMessage *)outgoingMessage
completionHandler:(void (^)(NSError *_Nullable error))completionHandler
{
2018-11-02 16:33:05 +01:00
OWSAssertDebug(attachmentInfos.count > 0);
OWSAssertDebug(outgoingMessage);
dispatch_async([OWSDispatch attachmentsQueue], ^{
2018-11-02 16:33:05 +01:00
NSMutableArray<TSAttachmentStream *> *attachmentStreams = [NSMutableArray new];
for (OWSOutgoingAttachmentInfo *attachmentInfo in attachmentInfos) {
TSAttachmentStream *attachmentStream =
[[TSAttachmentStream alloc] initWithContentType:attachmentInfo.contentType
byteCount:(UInt32)attachmentInfo.dataSource.dataLength
sourceFilename:attachmentInfo.sourceFilename
caption:attachmentInfo.caption
albumMessageId:attachmentInfo.albumMessageId];
2018-11-02 16:33:05 +01:00
if (outgoingMessage.isVoiceMessage) {
attachmentStream.attachmentType = TSAttachmentTypeVoiceMessage;
}
2018-11-02 16:33:05 +01:00
if (![attachmentStream writeDataSource:attachmentInfo.dataSource]) {
OWSProdError([OWSAnalyticsEvents messageSenderErrorCouldNotWriteAttachment]);
NSError *error = OWSErrorMakeWriteAttachmentDataError();
completionHandler(error);
return;
}
[attachmentStreams addObject:attachmentStream];
}
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
2018-11-02 16:33:05 +01:00
for (TSAttachmentStream *attachmentStream in attachmentStreams) {
[outgoingMessage.attachmentIds addObject:attachmentStream.uniqueId];
if (attachmentStream.sourceFilename) {
outgoingMessage.attachmentFilenameMap[attachmentStream.uniqueId] = attachmentStream.sourceFilename;
}
}
[outgoingMessage saveWithTransaction:transaction];
2018-11-07 18:00:34 +01:00
for (TSAttachmentStream *attachmentStream in attachmentStreams) {
[attachmentStream saveWithTransaction:transaction];
}
}];
completionHandler(nil);
});
}
@end
NS_ASSUME_NONNULL_END