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

1763 lines
73 KiB
Mathematica
Raw Normal View History

//
2018-01-11 16:25:13 +01:00
// Copyright (c) 2018 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.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"
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
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 -
/**
* 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;
}
self.remainingRetries = 6;
_message = message;
_messageSender = messageSender;
_dbConnection = dbConnection;
_successHandler = successHandler;
_failureHandler = failureHandler;
return self;
}
#pragma mark - OWSOperation overrides
- (nullable NSError *)checkForPreconditionError
{
NSError *_Nullable error = [super checkForPreconditionError];
if (error) {
return error;
}
// Sanity check preconditions
if (self.message.hasAttachments) {
2018-07-16 19:11:00 +02:00
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction * transaction) {
TSAttachmentStream *attachmentStream
= (TSAttachmentStream *)[self.message attachmentWithTransaction:transaction];
OWSAssertDebug(attachmentStream);
OWSAssertDebug([attachmentStream isKindOfClass:[TSAttachmentStream class]]);
OWSAssertDebug(attachmentStream.serverId);
OWSAssertDebug(attachmentStream.isUploaded);
}];
}
return nil;
}
- (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
{
[self.message updateWithSendingError:error];
OWSLogDebug(@"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;
}
}
2017-11-15 19:21:31 +01:00
- (void)enqueueMessage:(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), ^{
__block NSArray<TSAttachmentStream *> *quotedThumbnailAttachments = @[];
__block TSAttachmentStream *_Nullable contactShareAvatarAttachment;
// 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) {
if (message.quotedMessage) {
quotedThumbnailAttachments =
[message.quotedMessage createThumbnailAttachmentsIfNecessaryWithTransaction:transaction];
}
if (message.contactShare.avatarAttachmentId != nil) {
TSAttachment *avatarAttachment = [message.contactShare avatarAttachmentWithTransaction:transaction];
if ([avatarAttachment isKindOfClass:[TSAttachmentStream class]]) {
contactShareAvatarAttachment = (TSAttachmentStream *)avatarAttachment;
} else {
OWSFailDebug(@"unexpected avatarAttachment: %@", avatarAttachment);
}
}
// All outgoing messages should be saved at the time they are enqueued.
[message saveWithTransaction:transaction];
2018-04-23 16:30:51 +02:00
// When we start a message send, all "failed" recipients should be marked as "sending".
[message updateWithMarkingAllUnsentRecipientsAsSendingWithTransaction:transaction];
}];
NSOperationQueue *sendingQueue = [self sendingQueueForMessage:message];
OWSSendMessageOperation *sendMessageOperation =
[[OWSSendMessageOperation alloc] initWithMessage:message
messageSender:self
dbConnection:self.dbConnection
success:successHandler
failure:failureHandler];
// TODO de-dupe attachment enque logic.
if (message.hasAttachments) {
OWSUploadOperation *uploadAttachmentOperation =
[[OWSUploadOperation alloc] initWithAttachmentId:message.attachmentIds.firstObject
dbConnection:self.dbConnection];
[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
// 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.
OWSAssertDebug(quotedThumbnailAttachments.count <= 1);
for (TSAttachmentStream *thumbnailAttachment in quotedThumbnailAttachments) {
OWSAssertDebug(message.quotedMessage);
OWSUploadOperation *uploadQuoteThumbnailOperation =
[[OWSUploadOperation alloc] initWithAttachmentId:thumbnailAttachment.uniqueId
dbConnection:self.dbConnection];
// TODO put attachment uploads on a (lowly) concurrent queue
[sendMessageOperation addDependency:uploadQuoteThumbnailOperation];
[sendingQueue addOperation:uploadQuoteThumbnailOperation];
}
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 (contactShareAvatarAttachment != nil) {
OWSAssertDebug(message.contactShare);
OWSUploadOperation *uploadAvatarOperation =
[[OWSUploadOperation alloc] initWithAttachmentId:contactShareAvatarAttachment.uniqueId
dbConnection:self.dbConnection];
// TODO put attachment uploads on a (lowly) concurrent queue
[sendMessageOperation addDependency:uploadAvatarOperation];
[sendingQueue addOperation:uploadAvatarOperation];
}
[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
}
2017-11-15 19:21:31 +01:00
- (void)enqueueTemporaryAttachment:(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];
};
2017-11-15 19:21:31 +01:00
[self enqueueAttachment:dataSource
contentType:contentType
sourceFilename: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
}
2017-11-15 19:21:31 +01:00
- (void)enqueueAttachment:(DataSource *)dataSource
contentType:(NSString *)contentType
sourceFilename:(nullable NSString *)sourceFilename
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);
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 attachmentsQueue], ^{
2017-11-08 18:56:55 +01:00
TSAttachmentStream *attachmentStream =
[[TSAttachmentStream alloc] initWithContentType:contentType
byteCount:(UInt32)dataSource.dataLength
sourceFilename:sourceFilename];
2017-05-12 15:11:43 +02:00
if (message.isVoiceMessage) {
attachmentStream.attachmentType = TSAttachmentTypeVoiceMessage;
}
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 (![attachmentStream writeDataSource:dataSource]) {
OWSProdError([OWSAnalyticsEvents messageSenderErrorCouldNotWriteAttachment]);
NSError *error = OWSErrorMakeWriteAttachmentDataError();
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 failureHandler(error);
}
[attachmentStream save];
[message.attachmentIds addObject:attachmentStream.uniqueId];
if (sourceFilename) {
message.attachmentFilenameMap[attachmentStream.uniqueId] = sourceFilename;
2017-04-13 18:54:03 +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
2017-11-15 19:21:31 +01:00
[self enqueueMessage:message success:successHandler failure: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
});
}
- (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]]) {
[recipientIds addObject:[TSAccountManager localNumber]];
} 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-10-04 20:39:40 +02:00
if ([recipientIds containsObject:TSAccountManager.localNumber]) {
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-10-04 20:39:40 +02:00
if ([recipientIds containsObject:TSAccountManager.localNumber]) {
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-11-07 21:24:11 +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-10-04 20:39:40 +02:00
TSThread *_Nullable thread = message.thread;
// In the "self-send" special case, we ony need to send a sync message with a delivery receipt.
if ([thread isKindOfClass:[TSContactThread class]] &&
[((TSContactThread *)thread).contactIdentifier isEqualToString:[TSAccountManager localNumber]]) {
// Send to self.
OWSAssertDebug(message.recipientIds.count == 1);
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *recipientId in message.sendingRecipientIds) {
[message updateWithReadRecipientId:recipientId
readTimestamp:message.timestampForSorting
transaction: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
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
}];
}
2018-10-03 14:55:14 +02:00
- (nullable NSArray<NSDictionary *> *)deviceMessagesForMessageSendSafe:(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_deviceMessagesForMessageSendUnsafe: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: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
if (messageSend.remainingAttempts == 0) {
OWSLogWarn(@"Terminal failure to build any device messages. Giving up with exception: %@", 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
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
// Since we've already repeatedly failed to build messages, it's unlikely that repeating the whole process
// will succeed.
[error setIsRetryable:NO];
2018-10-03 14:55:14 +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
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 =
2018-10-05 16:41:10 +02:00
[self deviceMessagesForMessageSendSafe: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);
if (messageSend.isUDSend) {
OWSAssertDebug([messageType isEqualToNumber:@(TSUnidentifiedSenderMessageType)]);
} else {
OWSAssertDebug([messageType isEqualToNumber:@(TSEncryptedWhisperMessageType)] ||
[messageType isEqualToNumber:@(TSPreKeyWhisperMessageType)]);
}
}
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.");
2018-03-02 05:31:41 +01:00
}
2018-10-26 20:07:17 +02:00
OWSRequestMaker *requestMaker = [[OWSRequestMaker alloc] initWithLabel:@"Message Send"
requestFactoryBlock:^(SMKUDAccessKey *_Nullable udAccessKey) {
2018-10-19 18:25:02 +02:00
return [OWSRequestFactory submitMessageRequestWithRecipient:recipient.recipientId
messages:deviceMessages
timeStamp:message.timestamp
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:recipient.recipientId
2018-10-30 17:06:20 +01:00
udAccess:messageSend.udAccess
2018-11-29 19:10:40 +01:00
canFailoverUDAuth:NO];
2018-10-19 18:25:02 +02:00
[[requestMaker makeRequestObjc]
.then(^(OWSRequestMakerResult *result) {
dispatch_async([OWSDispatch sendingQueue], ^{
2018-11-14 14:30:25 +01:00
[self messageSendDidSucceed:messageSend
deviceMessages:deviceMessages
wasSentByUD:result.wasSentByUD
wasSentByWebsocket:result.wasSentByWebsocket];
2018-10-19 18:25:02 +02:00
});
})
.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]) {
2018-10-19 18:25:02 +02:00
statusCode = error.code;
NSError *_Nullable underlyingError = error.userInfo[NSUnderlyingErrorKey];
if (underlyingError) {
responseData
= underlyingError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
} else {
OWSFailDebug(@"Missing underlying error: %@", error);
}
} else {
OWSFailDebug(@"Unexpected error: %@", error);
}
2018-10-19 18:25:02 +02:00
[self messageSendDidFail:messageSend
deviceMessages:deviceMessages
statusCode:statusCode
error:error
responseData:responseData];
});
2018-10-19 18:25:02 +02:00
}) retainUntilComplete];
2018-05-18 20:35:00 +02:00
}
2018-03-02 05:31:41 +01:00
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;
2018-11-14 14:30:25 +01:00
OWSLogInfo(@"Message send succeeded; wasSentByUD: %d, wasSentByWebsocket: %d.", wasSentByUD, wasSentByWebsocket);
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;
OWSLogInfo(@"sending to recipient: %@, failed with error.", 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-05-18 20:35:00 +02:00
// Since we've already repeatedly failed to send to the messaging API,
// it's unlikely that repeating the whole process will succeed.
[responseError setIsRetryable:NO];
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
2018-10-03 14:55:14 +02:00
TSThread *_Nullable thread = messageSend.thread;
OWSAssertDebug(thread);
2018-07-17 16:09:01 +02:00
dispatch_async([OWSDispatch sendingQueue], ^{
[self unregisteredRecipient:recipient message:message thread:thread];
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) {
NSString *localNumber = [TSAccountManager localNumber];
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-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
{
[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();
}
[self
sendSyncTranscriptForMessage:message
success:^{
// TODO: We might send to a recipient, then to another recipient on retry.
// To ensure desktop receives all "delivery status" info, we might
// want to send a transcript after every send that reaches _any_
// new recipients.
[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
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];
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_deviceMessagesForMessageSendUnsafe:(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);
NSMutableArray<NSNumber *> *deviceIds = [recipient.devices mutableCopy];
OWSAssertDebug(deviceIds);
if (messageSend.isUDSend && messageSend.isLocalNumber) {
2018-10-22 19:57:21 +02:00
OWSLogVerbose(@"Adding device message for UD send to local device.");
OWSAssertDebug(![deviceIds containsObject:@(OWSDevicePrimaryDeviceId)]);
[deviceIds addObject:@(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.
[self throws_ensureRecipientHasSessionForMessageSend:messageSend deviceId:deviceId];
2018-10-18 16:06:03 +02:00
__block NSDictionary *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;
}
2018-10-03 20:01:55 +02:00
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];
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
- (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);
// 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-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;
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
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) {
2018-10-05 16:32:32 +02:00
[[[TSInfoMessage alloc] initWithTimestamp:message.timestamp
inThread:thread
messageType:TSInfoMessageTypeGroupQuit
customMessage:message.customMessage] save];
} else {
[[[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
NS_ASSUME_NONNULL_END