|
|
@ -39,6 +39,7 @@
|
|
|
|
#import "TSOutgoingMessage.h"
|
|
|
|
#import "TSOutgoingMessage.h"
|
|
|
|
#import "TSPreKeyManager.h"
|
|
|
|
#import "TSPreKeyManager.h"
|
|
|
|
#import "TSQuotedMessage.h"
|
|
|
|
#import "TSQuotedMessage.h"
|
|
|
|
|
|
|
|
#import "TSRequest.h"
|
|
|
|
#import "TSSocketManager.h"
|
|
|
|
#import "TSSocketManager.h"
|
|
|
|
#import "TSThread.h"
|
|
|
|
#import "TSThread.h"
|
|
|
|
#import <AxolotlKit/AxolotlExceptions.h>
|
|
|
|
#import <AxolotlKit/AxolotlExceptions.h>
|
|
|
@ -47,8 +48,10 @@
|
|
|
|
#import <AxolotlKit/SessionBuilder.h>
|
|
|
|
#import <AxolotlKit/SessionBuilder.h>
|
|
|
|
#import <AxolotlKit/SessionCipher.h>
|
|
|
|
#import <AxolotlKit/SessionCipher.h>
|
|
|
|
#import <PromiseKit/AnyPromise.h>
|
|
|
|
#import <PromiseKit/AnyPromise.h>
|
|
|
|
|
|
|
|
#import <SignalCoreKit/NSData+OWS.h>
|
|
|
|
#import <SignalCoreKit/NSDate+OWS.h>
|
|
|
|
#import <SignalCoreKit/NSDate+OWS.h>
|
|
|
|
#import <SignalCoreKit/Threading.h>
|
|
|
|
#import <SignalCoreKit/Threading.h>
|
|
|
|
|
|
|
|
#import <SignalMetadataKit/SignalMetadataKit-Swift.h>
|
|
|
|
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
|
|
|
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
|
|
|
|
|
|
|
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
@ -195,7 +198,8 @@ void AssertIsOnSendingQueue()
|
|
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
int const OWSMessageSenderRetryAttempts = 3;
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
NSString *const OWSMessageSenderInvalidDeviceException = @"InvalidDeviceException";
|
|
|
|
NSString *const OWSMessageSenderInvalidDeviceException = @"InvalidDeviceException";
|
|
|
|
NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
|
|
|
|
|
|
|
@ -207,6 +211,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
@implementation OWSMessageSender
|
|
|
|
@implementation OWSMessageSender
|
|
|
|
|
|
|
|
|
|
|
|
- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage
|
|
|
|
- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage
|
|
|
@ -225,6 +231,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
return self;
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark -Dependencies
|
|
|
|
|
|
|
|
|
|
|
|
- (id<ContactsManagerProtocol>)contactsManager
|
|
|
|
- (id<ContactsManagerProtocol>)contactsManager
|
|
|
|
{
|
|
|
|
{
|
|
|
|
OWSAssertDebug(SSKEnvironment.shared.contactsManager);
|
|
|
|
OWSAssertDebug(SSKEnvironment.shared.contactsManager);
|
|
|
@ -246,6 +254,20 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
return SSKEnvironment.shared.networkManager;
|
|
|
|
return SSKEnvironment.shared.networkManager;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (id<OWSUDManager>)udManager
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
OWSAssertDebug(SSKEnvironment.shared.udManager);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return SSKEnvironment.shared.udManager;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (TSAccountManager *)tsAccountManager
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return TSAccountManager.sharedInstance;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
- (NSOperationQueue *)sendingQueueForMessage:(TSOutgoingMessage *)message
|
|
|
|
- (NSOperationQueue *)sendingQueueForMessage:(TSOutgoingMessage *)message
|
|
|
|
{
|
|
|
|
{
|
|
|
|
OWSAssertDebug(message);
|
|
|
|
OWSAssertDebug(message);
|
|
|
@ -435,23 +457,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSArray<SignalRecipient *> *)signalRecipientsForRecipientIds:(NSArray<NSString *> *)recipientIds
|
|
|
|
|
|
|
|
message:(TSOutgoingMessage *)message
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
OWSAssertDebug(recipientIds);
|
|
|
|
|
|
|
|
OWSAssertDebug(message);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)sendMessageToService:(TSOutgoingMessage *)message
|
|
|
|
- (void)sendMessageToService:(TSOutgoingMessage *)message
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
failure:(RetryableFailureHandler)failureHandler
|
|
|
|
failure:(RetryableFailureHandler)failureHandler
|
|
|
@ -459,7 +464,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
|
TSThread *_Nullable thread = message.thread;
|
|
|
|
TSThread *_Nullable thread = message.thread;
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: It would be nice to combine the "contact" and "group" send logic here.
|
|
|
|
// In the "self-send" special case, we ony need to send a sync message with a delivery receipt.
|
|
|
|
if ([thread isKindOfClass:[TSContactThread class]] &&
|
|
|
|
if ([thread isKindOfClass:[TSContactThread class]] &&
|
|
|
|
[((TSContactThread *)thread).contactIdentifier isEqualToString:[TSAccountManager localNumber]]) {
|
|
|
|
[((TSContactThread *)thread).contactIdentifier isEqualToString:[TSAccountManager localNumber]]) {
|
|
|
|
// Send to self.
|
|
|
|
// Send to self.
|
|
|
@ -476,8 +481,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
|
|
|
|
|
|
|
|
successHandler();
|
|
|
|
successHandler();
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
} else if ([thread isKindOfClass:[TSGroupThread class]]) {
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSMutableSet<NSString *> *recipientIds = [NSMutableSet new];
|
|
|
|
|
|
|
|
if (thread.isGroupThread) {
|
|
|
|
TSGroupThread *gThread = (TSGroupThread *)thread;
|
|
|
|
TSGroupThread *gThread = (TSGroupThread *)thread;
|
|
|
|
|
|
|
|
|
|
|
|
// Send to the intersection of:
|
|
|
|
// Send to the intersection of:
|
|
|
@ -491,9 +498,45 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
// * The recipient is still in the group.
|
|
|
|
// * The recipient is still in the group.
|
|
|
|
// * The recipient is in the "sending" state.
|
|
|
|
// * The recipient is in the "sending" state.
|
|
|
|
|
|
|
|
|
|
|
|
NSMutableSet<NSString *> *sendingRecipientIds = [NSMutableSet setWithArray:message.sendingRecipientIds];
|
|
|
|
[recipientIds addObjectsFromArray:message.sendingRecipientIds];
|
|
|
|
[sendingRecipientIds intersectSet:[NSSet setWithArray:gThread.groupModel.groupMemberIds]];
|
|
|
|
// Only send to members in the latest known group member list.
|
|
|
|
[sendingRecipientIds minusSet:[NSSet setWithArray:self.blockingManager.blockedPhoneNumbers]];
|
|
|
|
[recipientIds intersectSet:[NSSet setWithArray:gThread.groupModel.groupMemberIds]];
|
|
|
|
|
|
|
|
} else if ([message isKindOfClass:[OWSOutgoingSyncMessage class]]) {
|
|
|
|
|
|
|
|
[recipientIds addObject:[TSAccountManager localNumber]];
|
|
|
|
|
|
|
|
} 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);
|
|
|
|
|
|
|
|
NSError *error = OWSErrorMakeMessageSendFailedToBlockListError();
|
|
|
|
|
|
|
|
// No need to retry - the user will continue to be blocked.
|
|
|
|
|
|
|
|
[error setIsRetryable:NO];
|
|
|
|
|
|
|
|
failureHandler(error);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[recipientIds addObject:recipientContactId];
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Neither a group nor contact thread? This should never happen.
|
|
|
|
|
|
|
|
OWSFailDebug(@"Unknown message type: %@", [message class]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
|
|
|
|
|
|
|
|
[error setIsRetryable:NO];
|
|
|
|
|
|
|
|
failureHandler(error);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[recipientIds minusSet:[NSSet setWithArray:self.blockingManager.blockedPhoneNumbers]];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ([recipientIds containsObject:TSAccountManager.localNumber]) {
|
|
|
|
|
|
|
|
OWSFailDebug(@"Message send recipients should not include self.");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
[recipientIds removeObject:TSAccountManager.localNumber];
|
|
|
|
|
|
|
|
|
|
|
|
// Mark skipped recipients as such. We skip because:
|
|
|
|
// Mark skipped recipients as such. We skip because:
|
|
|
|
//
|
|
|
|
//
|
|
|
@ -502,7 +545,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// Elsewhere, we skip recipient if their Signal account has been deactivated.
|
|
|
|
// Elsewhere, we skip recipient if their Signal account has been deactivated.
|
|
|
|
NSMutableSet<NSString *> *obsoleteRecipientIds = [NSMutableSet setWithArray:message.sendingRecipientIds];
|
|
|
|
NSMutableSet<NSString *> *obsoleteRecipientIds = [NSMutableSet setWithArray:message.sendingRecipientIds];
|
|
|
|
[obsoleteRecipientIds minusSet:sendingRecipientIds];
|
|
|
|
[obsoleteRecipientIds minusSet:recipientIds];
|
|
|
|
if (obsoleteRecipientIds.count > 0) {
|
|
|
|
if (obsoleteRecipientIds.count > 0) {
|
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
for (NSString *recipientId in obsoleteRecipientIds) {
|
|
|
|
for (NSString *recipientId in obsoleteRecipientIds) {
|
|
|
@ -512,102 +555,51 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
}];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (sendingRecipientIds.count < 1) {
|
|
|
|
if (recipientIds.count < 1) {
|
|
|
|
// All recipients are already sent or can be skipped.
|
|
|
|
// All recipients are already sent or can be skipped.
|
|
|
|
successHandler();
|
|
|
|
successHandler();
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSArray<SignalRecipient *> *recipients =
|
|
|
|
NSMutableArray<OWSMessageSend *> *messageSends = [NSMutableArray new];
|
|
|
|
[self signalRecipientsForRecipientIds:sendingRecipientIds.allObjects message:message];
|
|
|
|
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
|
|
|
OWSAssertDebug(recipients.count == sendingRecipientIds.count);
|
|
|
|
for (NSString *recipientId in recipientIds) {
|
|
|
|
|
|
|
|
SignalRecipient *recipient =
|
|
|
|
[self groupSend:recipients message:message thread:gThread success:successHandler failure:failureHandler];
|
|
|
|
[SignalRecipient getOrBuildUnsavedRecipientForRecipientId:recipientId transaction:transaction];
|
|
|
|
|
|
|
|
OWSMessageSend *messageSend =
|
|
|
|
} else if ([thread isKindOfClass:[TSContactThread class]]
|
|
|
|
[[OWSMessageSend alloc] initWithMessage:message
|
|
|
|
|| [message isKindOfClass:[OWSOutgoingSyncMessage class]]) {
|
|
|
|
thread:thread
|
|
|
|
|
|
|
|
recipient:recipient
|
|
|
|
TSContactThread *contactThread = (TSContactThread *)thread;
|
|
|
|
udManager:self.udManager
|
|
|
|
|
|
|
|
localNumber:self.tsAccountManager.localNumber];
|
|
|
|
NSString *recipientContactId
|
|
|
|
[messageSends addObject:messageSend];
|
|
|
|
= ([message isKindOfClass:[OWSOutgoingSyncMessage class]] ? [TSAccountManager localNumber]
|
|
|
|
|
|
|
|
: contactThread.contactIdentifier);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
NSError *error = OWSErrorMakeMessageSendFailedToBlockListError();
|
|
|
|
|
|
|
|
// No need to retry - the user will continue to be blocked.
|
|
|
|
|
|
|
|
[error setIsRetryable:NO];
|
|
|
|
|
|
|
|
failureHandler(error);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSArray<SignalRecipient *> *recipients =
|
|
|
|
|
|
|
|
[self signalRecipientsForRecipientIds:@[recipientContactId] message:message];
|
|
|
|
|
|
|
|
OWSAssertDebug(recipients.count == 1);
|
|
|
|
|
|
|
|
SignalRecipient *recipient = recipients.firstObject;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!recipient) {
|
|
|
|
|
|
|
|
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
|
|
|
|
|
|
|
|
OWSLogWarn(@"recipient contact still not found after attempting lookup.");
|
|
|
|
|
|
|
|
// No need to repeat trying to find a failure. Apart from repeatedly failing, it would also cause us to
|
|
|
|
|
|
|
|
// print redundant error messages.
|
|
|
|
|
|
|
|
[error setIsRetryable:NO];
|
|
|
|
|
|
|
|
failureHandler(error);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
OWSAssertDebug(messageSends.count == recipientIds.count);
|
|
|
|
|
|
|
|
OWSAssertDebug(messageSends.count > 0);
|
|
|
|
|
|
|
|
|
|
|
|
[self sendMessageToService:message
|
|
|
|
[self sendWithMessageSends:messageSends
|
|
|
|
recipient:recipient
|
|
|
|
isGroupSend:thread.isGroupThread
|
|
|
|
thread:thread
|
|
|
|
|
|
|
|
attempts:OWSMessageSenderRetryAttempts
|
|
|
|
|
|
|
|
useWebsocketIfAvailable:YES
|
|
|
|
|
|
|
|
success:successHandler
|
|
|
|
success:successHandler
|
|
|
|
failure:failureHandler];
|
|
|
|
failure:failureHandler];
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Neither a group nor contact thread? This should never happen.
|
|
|
|
|
|
|
|
OWSFailDebug(@"Unknown message type: %@", [message class]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
|
|
|
|
|
|
|
|
[error setIsRetryable:NO];
|
|
|
|
|
|
|
|
failureHandler(error);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)groupSend:(NSArray<SignalRecipient *> *)recipients
|
|
|
|
- (void)sendWithMessageSends:(NSArray<OWSMessageSend *> *)messageSends
|
|
|
|
message:(TSOutgoingMessage *)message
|
|
|
|
isGroupSend:(BOOL)isGroupSend
|
|
|
|
thread:(TSThread *)thread
|
|
|
|
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
failure:(RetryableFailureHandler)failureHandler
|
|
|
|
failure:(RetryableFailureHandler)failureHandler
|
|
|
|
{
|
|
|
|
{
|
|
|
|
[self saveGroupMessage:message inThread:thread];
|
|
|
|
OWSAssertDebug(messageSends.count > 0);
|
|
|
|
|
|
|
|
AssertIsOnSendingQueue();
|
|
|
|
|
|
|
|
|
|
|
|
NSMutableArray<AnyPromise *> *sendPromises = [NSMutableArray array];
|
|
|
|
NSMutableArray<AnyPromise *> *sendPromises = [NSMutableArray array];
|
|
|
|
NSMutableArray<NSError *> *sendErrors = [NSMutableArray array];
|
|
|
|
NSMutableArray<NSError *> *sendErrors = [NSMutableArray array];
|
|
|
|
|
|
|
|
|
|
|
|
for (SignalRecipient *recipient in recipients) {
|
|
|
|
for (OWSMessageSend *messageSend in messageSends) {
|
|
|
|
NSString *recipientId = recipient.recipientId;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// We don't need to send the message to ourselves...
|
|
|
|
|
|
|
|
if ([recipientId isEqualToString:[TSAccountManager localNumber]]) {
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ...otherwise we send.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// For group sends, we're using chained promises to make the code more readable.
|
|
|
|
// For group sends, we're using chained promises to make the code more readable.
|
|
|
|
AnyPromise *sendPromise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
|
|
|
|
AnyPromise *sendPromise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
|
|
|
|
[self sendMessageToService:message
|
|
|
|
[self sendMessageToRecipient:messageSend
|
|
|
|
recipient:recipient
|
|
|
|
|
|
|
|
thread:thread
|
|
|
|
|
|
|
|
attempts:OWSMessageSenderRetryAttempts
|
|
|
|
|
|
|
|
useWebsocketIfAvailable:YES
|
|
|
|
|
|
|
|
success:^{
|
|
|
|
success:^{
|
|
|
|
// The value doesn't matter, we just need any non-NSError value.
|
|
|
|
// The value doesn't matter, we just need any non-NSError value.
|
|
|
|
resolve(@(1));
|
|
|
|
resolve(@(1));
|
|
|
@ -643,7 +635,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
// Some errors should be ignored when sending messages
|
|
|
|
// Some errors should be ignored when sending messages
|
|
|
|
// to groups. See discussion on
|
|
|
|
// to groups. See discussion on
|
|
|
|
// NSError (OWSMessageSender) category.
|
|
|
|
// NSError (OWSMessageSender) category.
|
|
|
|
if ([error shouldBeIgnoredForGroups]) {
|
|
|
|
if (isGroupSend && [error shouldBeIgnoredForGroups]) {
|
|
|
|
continue;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -664,7 +656,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If any of the group send errors are retryable, we want to retry.
|
|
|
|
// If any of the send errors are retryable, we want to retry.
|
|
|
|
// Therefore, prefer to propagate a retryable error.
|
|
|
|
// Therefore, prefer to propagate a retryable error.
|
|
|
|
if (firstRetryableError) {
|
|
|
|
if (firstRetryableError) {
|
|
|
|
return failureHandler(firstRetryableError);
|
|
|
|
return failureHandler(firstRetryableError);
|
|
|
@ -674,7 +666,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
// If we only received errors that we should ignore,
|
|
|
|
// If we only received errors that we should ignore,
|
|
|
|
// consider this send a success, unless the message could
|
|
|
|
// consider this send a success, unless the message could
|
|
|
|
// not be sent to any recipient.
|
|
|
|
// not be sent to any recipient.
|
|
|
|
if (message.sentRecipientsCount == 0) {
|
|
|
|
if (messageSends.lastObject.message.sentRecipientsCount == 0) {
|
|
|
|
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageSendNoValidRecipients,
|
|
|
|
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageSendNoValidRecipients,
|
|
|
|
NSLocalizedString(@"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS",
|
|
|
|
NSLocalizedString(@"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS",
|
|
|
|
@"Error indicating that an outgoing message had no valid recipients."));
|
|
|
|
@"Error indicating that an outgoing message had no valid recipients."));
|
|
|
@ -713,60 +705,19 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
}];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)sendMessageToService:(TSOutgoingMessage *)message
|
|
|
|
- (nullable NSArray<NSDictionary *> *)deviceMessagesForMessageSendSafe:(OWSMessageSend *)messageSend
|
|
|
|
recipient:(SignalRecipient *)recipient
|
|
|
|
error:(NSError **)errorHandle
|
|
|
|
thread:(nullable TSThread *)thread
|
|
|
|
|
|
|
|
attempts:(int)remainingAttemptsParam
|
|
|
|
|
|
|
|
useWebsocketIfAvailable:(BOOL)useWebsocketIfAvailable
|
|
|
|
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
|
|
|
|
failure:(RetryableFailureHandler)failureHandler
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
OWSAssertDebug(message);
|
|
|
|
OWSAssertDebug(messageSend);
|
|
|
|
OWSAssertDebug(recipient);
|
|
|
|
OWSAssertDebug(errorHandle);
|
|
|
|
OWSAssertDebug(thread || [message isKindOfClass:[OWSOutgoingSyncMessage class]]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
OWSLogInfo(@"attempting to send message: %@, timestamp: %llu, recipient: %@",
|
|
|
|
|
|
|
|
message.class,
|
|
|
|
|
|
|
|
message.timestamp,
|
|
|
|
|
|
|
|
recipient.uniqueId);
|
|
|
|
|
|
|
|
AssertIsOnSendingQueue();
|
|
|
|
AssertIsOnSendingQueue();
|
|
|
|
|
|
|
|
|
|
|
|
if ([TSPreKeyManager isAppLockedDueToPreKeyUpdateFailures]) {
|
|
|
|
SignalRecipient *recipient = messageSend.recipient;
|
|
|
|
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];
|
|
|
|
|
|
|
|
return failureHandler(error);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
failure:^(NSError *error) {
|
|
|
|
|
|
|
|
OWSLogWarn(@"Failed to update prekeys with the server: %@", error);
|
|
|
|
|
|
|
|
return failureHandler(error);
|
|
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (remainingAttemptsParam <= 0) {
|
|
|
|
|
|
|
|
// We should always fail with a specific error.
|
|
|
|
|
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorGenericSendFailure]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
|
|
|
|
|
|
|
|
[error setIsRetryable:YES];
|
|
|
|
|
|
|
|
return failureHandler(error);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int remainingAttempts = remainingAttemptsParam - 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSArray<NSDictionary *> *deviceMessages;
|
|
|
|
NSArray<NSDictionary *> *deviceMessages;
|
|
|
|
@try {
|
|
|
|
@try {
|
|
|
|
deviceMessages = [self deviceMessages:message recipient:recipient];
|
|
|
|
deviceMessages = [self deviceMessagesForMessageSendUnsafe:messageSend];
|
|
|
|
} @catch (NSException *exception) {
|
|
|
|
} @catch (NSException *exception) {
|
|
|
|
deviceMessages = @[];
|
|
|
|
|
|
|
|
if ([exception.name isEqualToString:UntrustedIdentityKeyException]) {
|
|
|
|
if ([exception.name isEqualToString:UntrustedIdentityKeyException]) {
|
|
|
|
// This *can* happen under normal usage, but it should happen relatively rarely.
|
|
|
|
// 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
|
|
|
|
// We expect it to happen whenever Bob reinstalls, and Alice messages Bob before
|
|
|
@ -788,67 +739,127 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
[error setIsRetryable:NO];
|
|
|
|
[error setIsRetryable:NO];
|
|
|
|
// Avoid the "Too many failures with this contact" error rate limiting.
|
|
|
|
// Avoid the "Too many failures with this contact" error rate limiting.
|
|
|
|
[error setIsFatal:YES];
|
|
|
|
[error setIsFatal:YES];
|
|
|
|
|
|
|
|
*errorHandle = error;
|
|
|
|
|
|
|
|
|
|
|
|
PreKeyBundle *_Nullable newKeyBundle = exception.userInfo[TSInvalidPreKeyBundleKey];
|
|
|
|
PreKeyBundle *_Nullable newKeyBundle = exception.userInfo[TSInvalidPreKeyBundleKey];
|
|
|
|
if (newKeyBundle == nil) {
|
|
|
|
if (newKeyBundle == nil) {
|
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorMissingNewPreKeyBundle]);
|
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorMissingNewPreKeyBundle]);
|
|
|
|
failureHandler(error);
|
|
|
|
return nil;
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (![newKeyBundle isKindOfClass:[PreKeyBundle class]]) {
|
|
|
|
if (![newKeyBundle isKindOfClass:[PreKeyBundle class]]) {
|
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorUnexpectedKeyBundle]);
|
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorUnexpectedKeyBundle]);
|
|
|
|
failureHandler(error);
|
|
|
|
return nil;
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSData *newIdentityKeyWithVersion = newKeyBundle.identityKey;
|
|
|
|
NSData *newIdentityKeyWithVersion = newKeyBundle.identityKey;
|
|
|
|
|
|
|
|
|
|
|
|
if (![newIdentityKeyWithVersion isKindOfClass:[NSData class]]) {
|
|
|
|
if (![newIdentityKeyWithVersion isKindOfClass:[NSData class]]) {
|
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorInvalidIdentityKeyType]);
|
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorInvalidIdentityKeyType]);
|
|
|
|
failureHandler(error);
|
|
|
|
return nil;
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO migrate to storing the full 33 byte representation of the identity key.
|
|
|
|
// TODO migrate to storing the full 33 byte representation of the identity key.
|
|
|
|
if (newIdentityKeyWithVersion.length != kIdentityKeyLength) {
|
|
|
|
if (newIdentityKeyWithVersion.length != kIdentityKeyLength) {
|
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorInvalidIdentityKeyLength]);
|
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorInvalidIdentityKeyLength]);
|
|
|
|
failureHandler(error);
|
|
|
|
return nil;
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSData *newIdentityKey = [newIdentityKeyWithVersion removeKeyType];
|
|
|
|
NSData *newIdentityKey = [newIdentityKeyWithVersion removeKeyType];
|
|
|
|
[[OWSIdentityManager sharedManager] saveRemoteIdentity:newIdentityKey recipientId:recipient.recipientId];
|
|
|
|
[[OWSIdentityManager sharedManager] saveRemoteIdentity:newIdentityKey recipientId:recipient.recipientId];
|
|
|
|
|
|
|
|
|
|
|
|
failureHandler(error);
|
|
|
|
return nil;
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ([exception.name isEqualToString:OWSMessageSenderRateLimitedException]) {
|
|
|
|
if ([exception.name isEqualToString:OWSMessageSenderRateLimitedException]) {
|
|
|
|
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeSignalServiceRateLimited,
|
|
|
|
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeSignalServiceRateLimited,
|
|
|
|
NSLocalizedString(@"FAILED_SENDING_BECAUSE_RATE_LIMIT",
|
|
|
|
NSLocalizedString(@"FAILED_SENDING_BECAUSE_RATE_LIMIT",
|
|
|
|
@"action sheet header when re-sending message which failed because of too many attempts"));
|
|
|
|
@"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.
|
|
|
|
// We're already rate-limited. No need to exacerbate the problem.
|
|
|
|
[error setIsRetryable:NO];
|
|
|
|
[error setIsRetryable:NO];
|
|
|
|
// Avoid exacerbating the rate limiting.
|
|
|
|
// Avoid exacerbating the rate limiting.
|
|
|
|
[error setIsFatal:YES];
|
|
|
|
[error setIsFatal:YES];
|
|
|
|
return failureHandler(error);
|
|
|
|
*errorHandle = error;
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (remainingAttempts == 0) {
|
|
|
|
if (messageSend.remainingAttempts == 0) {
|
|
|
|
OWSLogWarn(@"Terminal failure to build any device messages. Giving up with exception: %@", exception);
|
|
|
|
OWSLogWarn(@"Terminal failure to build any device messages. Giving up with exception: %@", exception);
|
|
|
|
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
|
|
|
|
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
|
|
|
|
// Since we've already repeatedly failed to build messages, it's unlikely that repeating the whole process
|
|
|
|
// Since we've already repeatedly failed to build messages, it's unlikely that repeating the whole process
|
|
|
|
// will succeed.
|
|
|
|
// will succeed.
|
|
|
|
[error setIsRetryable:NO];
|
|
|
|
[error setIsRetryable:NO];
|
|
|
|
|
|
|
|
*errorHandle = error;
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
OWSLogWarn(@"Could not build device messages: %@", exception);
|
|
|
|
|
|
|
|
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
|
|
|
|
|
|
|
|
[error setIsRetryable:YES];
|
|
|
|
|
|
|
|
*errorHandle = error;
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return deviceMessages;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)sendMessageToRecipient:(OWSMessageSend *)messageSend
|
|
|
|
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
|
|
|
|
failure:(RetryableFailureHandler)failureHandler
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
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];
|
|
|
|
return failureHandler(error);
|
|
|
|
return failureHandler(error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
failure:^(NSError *error) {
|
|
|
|
|
|
|
|
OWSLogWarn(@"Failed to update prekeys with the server: %@", error);
|
|
|
|
|
|
|
|
return failureHandler(error);
|
|
|
|
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NSString *localNumber = [TSAccountManager localNumber];
|
|
|
|
if (messageSend.remainingAttempts <= 0) {
|
|
|
|
BOOL isLocalNumber = [localNumber isEqualToString:recipient.uniqueId];
|
|
|
|
// We should always fail with a specific error.
|
|
|
|
if (isLocalNumber) {
|
|
|
|
OWSProdFail([OWSAnalyticsEvents messageSenderErrorGenericSendFailure]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
|
|
|
|
|
|
|
|
[error setIsRetryable:YES];
|
|
|
|
|
|
|
|
return failureHandler(error);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Consume an attempt.
|
|
|
|
|
|
|
|
messageSend.remainingAttempts = messageSend.remainingAttempts - 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSError *deviceMessagesError;
|
|
|
|
|
|
|
|
NSArray<NSDictionary *> *_Nullable deviceMessages =
|
|
|
|
|
|
|
|
[self deviceMessagesForMessageSendSafe:messageSend error:&deviceMessagesError];
|
|
|
|
|
|
|
|
if (deviceMessagesError || !deviceMessages) {
|
|
|
|
|
|
|
|
OWSAssertDebug(deviceMessagesError);
|
|
|
|
|
|
|
|
return failureHandler(deviceMessagesError);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (messageSend.isLocalNumber) {
|
|
|
|
OWSAssertDebug([message isKindOfClass:[OWSOutgoingSyncMessage class]]);
|
|
|
|
OWSAssertDebug([message isKindOfClass:[OWSOutgoingSyncMessage class]]);
|
|
|
|
// Messages sent to the "local number" should be sync messages.
|
|
|
|
// Messages sent to the "local number" should be sync messages.
|
|
|
|
//
|
|
|
|
//
|
|
|
@ -878,9 +889,9 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
OWSAssertDebug([message isKindOfClass:[OWSOutgoingSyncMessage class]]);
|
|
|
|
OWSAssertDebug([message isKindOfClass:[OWSOutgoingSyncMessage class]]);
|
|
|
|
|
|
|
|
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
|
// This emulates the completion logic of an actual successful save (see below).
|
|
|
|
// This emulates the completion logic of an actual successful send (see below).
|
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
[message updateWithSkippedRecipient:localNumber transaction:transaction];
|
|
|
|
[message updateWithSkippedRecipient:messageSend.localNumber transaction:transaction];
|
|
|
|
}];
|
|
|
|
}];
|
|
|
|
successHandler();
|
|
|
|
successHandler();
|
|
|
|
});
|
|
|
|
});
|
|
|
@ -918,51 +929,55 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
TSRequest *request = [OWSRequestFactory submitMessageRequestWithRecipient:recipient.uniqueId
|
|
|
|
TSRequest *request = [OWSRequestFactory submitMessageRequestWithRecipient:recipient.uniqueId
|
|
|
|
messages:deviceMessages
|
|
|
|
messages:deviceMessages
|
|
|
|
timeStamp:message.timestamp];
|
|
|
|
timeStamp:message.timestamp];
|
|
|
|
if (useWebsocketIfAvailable && TSSocketManager.canMakeRequests) {
|
|
|
|
|
|
|
|
|
|
|
|
BOOL isUDSend = messageSend.canUseUD && messageSend.udAccessKey != nil;
|
|
|
|
|
|
|
|
if (isUDSend) {
|
|
|
|
|
|
|
|
DDLogVerbose(@"UD send.");
|
|
|
|
|
|
|
|
request.shouldHaveAuthorizationHeaders = YES;
|
|
|
|
|
|
|
|
[request setValue:[messageSend.udAccessKey.keyData base64EncodedString] forKey:@"Unidentified-Access-Key"];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: UD sends over websocket.
|
|
|
|
|
|
|
|
if (messageSend.useWebsocketIfAvailable && TSSocketManager.canMakeRequests && !isUDSend) {
|
|
|
|
[TSSocketManager.sharedManager makeRequest:request
|
|
|
|
[TSSocketManager.sharedManager makeRequest:request
|
|
|
|
success:^(id _Nullable responseObject) {
|
|
|
|
success:^(id _Nullable responseObject) {
|
|
|
|
[self messageSendDidSucceed:message
|
|
|
|
[self messageSendDidSucceed:messageSend deviceMessages:deviceMessages success:successHandler];
|
|
|
|
recipient:recipient
|
|
|
|
|
|
|
|
isLocalNumber:isLocalNumber
|
|
|
|
|
|
|
|
deviceMessages:deviceMessages
|
|
|
|
|
|
|
|
success:successHandler];
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
failure:^(NSInteger statusCode, NSData *_Nullable responseData, NSError *error) {
|
|
|
|
failure:^(NSInteger statusCode, NSData *_Nullable responseData, NSError *error) {
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
|
OWSLogDebug(@"falling back to REST since first attempt failed.");
|
|
|
|
OWSLogDebug(@"Web socket send failed; failing over to REST.");
|
|
|
|
|
|
|
|
|
|
|
|
// Websockets can fail in different ways, so we don't decrement remainingAttempts for websocket
|
|
|
|
// Websockets can fail in different ways, so we don't decrement remainingAttempts for websocket
|
|
|
|
// failure. Instead we fall back to REST, which will decrement retries. e.g. after linking a new
|
|
|
|
// failure. Instead we fall back to REST, which will decrement retries. e.g. after linking a new
|
|
|
|
// device, sync messages will fail until the websocket re-opens.
|
|
|
|
// device, sync messages will fail until the websocket re-opens.
|
|
|
|
[self sendMessageToService:message
|
|
|
|
messageSend.useWebsocketIfAvailable = NO;
|
|
|
|
recipient:recipient
|
|
|
|
[self sendMessageToRecipient:messageSend success:successHandler failure:failureHandler];
|
|
|
|
thread:thread
|
|
|
|
|
|
|
|
attempts:remainingAttemptsParam
|
|
|
|
|
|
|
|
useWebsocketIfAvailable:NO
|
|
|
|
|
|
|
|
success:successHandler
|
|
|
|
|
|
|
|
failure:failureHandler];
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}];
|
|
|
|
}];
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
[self.networkManager makeRequest:request
|
|
|
|
[self.networkManager makeRequest:request
|
|
|
|
success:^(NSURLSessionDataTask *task, id responseObject) {
|
|
|
|
success:^(NSURLSessionDataTask *task, id responseObject) {
|
|
|
|
[self messageSendDidSucceed:message
|
|
|
|
[self messageSendDidSucceed:messageSend deviceMessages:deviceMessages success:successHandler];
|
|
|
|
recipient:recipient
|
|
|
|
|
|
|
|
isLocalNumber:isLocalNumber
|
|
|
|
|
|
|
|
deviceMessages:deviceMessages
|
|
|
|
|
|
|
|
success:successHandler];
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
failure:^(NSURLSessionDataTask *task, NSError *error) {
|
|
|
|
failure:^(NSURLSessionDataTask *task, NSError *error) {
|
|
|
|
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
|
|
|
|
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
|
|
|
|
NSInteger statusCode = response.statusCode;
|
|
|
|
NSInteger statusCode = response.statusCode;
|
|
|
|
NSData *_Nullable responseData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
|
|
|
|
NSData *_Nullable responseData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
|
|
|
|
|
|
|
|
|
|
|
|
[self messageSendDidFail:message
|
|
|
|
if (isUDSend && statusCode > 0) {
|
|
|
|
recipient:recipient
|
|
|
|
// If a UD send fails due to service response (as opposed to network
|
|
|
|
thread:thread
|
|
|
|
// failure), mark recipient as _not_ in UD mode, then retry.
|
|
|
|
isLocalNumber:isLocalNumber
|
|
|
|
//
|
|
|
|
|
|
|
|
// TODO: Do we want to discriminate based on exact error?
|
|
|
|
|
|
|
|
OWSLogDebug(@"UD send failed; failing over to non-UD send.");
|
|
|
|
|
|
|
|
[self.udManager removeUDRecipientId:recipient.uniqueId];
|
|
|
|
|
|
|
|
messageSend.canUseUD = NO;
|
|
|
|
|
|
|
|
[self sendMessageToRecipient:messageSend success:successHandler failure:failureHandler];
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[self messageSendDidFail:messageSend
|
|
|
|
deviceMessages:deviceMessages
|
|
|
|
deviceMessages:deviceMessages
|
|
|
|
remainingAttempts:remainingAttempts
|
|
|
|
|
|
|
|
statusCode:statusCode
|
|
|
|
statusCode:statusCode
|
|
|
|
error:error
|
|
|
|
error:error
|
|
|
|
responseData:responseData
|
|
|
|
responseData:responseData
|
|
|
@ -972,20 +987,19 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)messageSendDidSucceed:(TSOutgoingMessage *)message
|
|
|
|
- (void)messageSendDidSucceed:(OWSMessageSend *)messageSend
|
|
|
|
recipient:(SignalRecipient *)recipient
|
|
|
|
|
|
|
|
isLocalNumber:(BOOL)isLocalNumber
|
|
|
|
|
|
|
|
deviceMessages:(NSArray<NSDictionary *> *)deviceMessages
|
|
|
|
deviceMessages:(NSArray<NSDictionary *> *)deviceMessages
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
{
|
|
|
|
{
|
|
|
|
OWSAssertDebug(message);
|
|
|
|
OWSAssertDebug(messageSend);
|
|
|
|
OWSAssertDebug(recipient);
|
|
|
|
|
|
|
|
OWSAssertDebug(deviceMessages);
|
|
|
|
OWSAssertDebug(deviceMessages);
|
|
|
|
OWSAssertDebug(successHandler);
|
|
|
|
OWSAssertDebug(successHandler);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SignalRecipient *recipient = messageSend.recipient;
|
|
|
|
|
|
|
|
|
|
|
|
OWSLogInfo(@"Message send succeeded.");
|
|
|
|
OWSLogInfo(@"Message send succeeded.");
|
|
|
|
|
|
|
|
|
|
|
|
if (isLocalNumber && deviceMessages.count == 0) {
|
|
|
|
if (messageSend.isLocalNumber && deviceMessages.count == 0) {
|
|
|
|
OWSLogInfo(@"Sent a message with no device messages; clearing 'mayHaveLinkedDevices'.");
|
|
|
|
OWSLogInfo(@"Sent a message with no device messages; clearing 'mayHaveLinkedDevices'.");
|
|
|
|
// In order to avoid skipping necessary sync messages, the default value
|
|
|
|
// In order to avoid skipping necessary sync messages, the default value
|
|
|
|
// for mayHaveLinkedDevices is YES. Once we've successfully sent a
|
|
|
|
// for mayHaveLinkedDevices is YES. Once we've successfully sent a
|
|
|
@ -999,42 +1013,40 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
|
|
|
|
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
[message updateWithSentRecipient:recipient.uniqueId transaction:transaction];
|
|
|
|
[messageSend.message updateWithSentRecipient:messageSend.recipient.uniqueId transaction:transaction];
|
|
|
|
|
|
|
|
|
|
|
|
// If we've just delivered a message to a user, we know they
|
|
|
|
// If we've just delivered a message to a user, we know they
|
|
|
|
// have a valid Signal account.
|
|
|
|
// have a valid Signal account.
|
|
|
|
[SignalRecipient markRecipientAsRegisteredAndGet:recipient.recipientId transaction:transaction];
|
|
|
|
[SignalRecipient markRecipientAsRegisteredAndGet:recipient.recipientId transaction:transaction];
|
|
|
|
}];
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self handleMessageSentLocally:message];
|
|
|
|
[self handleMessageSentLocally:messageSend.message];
|
|
|
|
successHandler();
|
|
|
|
successHandler();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)messageSendDidFail:(TSOutgoingMessage *)message
|
|
|
|
- (void)messageSendDidFail:(OWSMessageSend *)messageSend
|
|
|
|
recipient:(SignalRecipient *)recipient
|
|
|
|
|
|
|
|
thread:(nullable TSThread *)thread
|
|
|
|
|
|
|
|
isLocalNumber:(BOOL)isLocalNumber
|
|
|
|
|
|
|
|
deviceMessages:(NSArray<NSDictionary *> *)deviceMessages
|
|
|
|
deviceMessages:(NSArray<NSDictionary *> *)deviceMessages
|
|
|
|
remainingAttempts:(int)remainingAttempts
|
|
|
|
|
|
|
|
statusCode:(NSInteger)statusCode
|
|
|
|
statusCode:(NSInteger)statusCode
|
|
|
|
error:(NSError *)responseError
|
|
|
|
error:(NSError *)responseError
|
|
|
|
responseData:(nullable NSData *)responseData
|
|
|
|
responseData:(nullable NSData *)responseData
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
success:(void (^)(void))successHandler
|
|
|
|
failure:(RetryableFailureHandler)failureHandler
|
|
|
|
failure:(RetryableFailureHandler)failureHandler
|
|
|
|
{
|
|
|
|
{
|
|
|
|
OWSAssertDebug(message);
|
|
|
|
OWSAssertDebug(messageSend);
|
|
|
|
OWSAssertDebug(recipient);
|
|
|
|
OWSAssertDebug(messageSend.thread || [messageSend.message isKindOfClass:[OWSOutgoingSyncMessage class]]);
|
|
|
|
OWSAssertDebug(thread || [message isKindOfClass:[OWSOutgoingSyncMessage class]]);
|
|
|
|
|
|
|
|
OWSAssertDebug(deviceMessages);
|
|
|
|
OWSAssertDebug(deviceMessages);
|
|
|
|
OWSAssertDebug(responseError);
|
|
|
|
OWSAssertDebug(responseError);
|
|
|
|
OWSAssertDebug(successHandler);
|
|
|
|
OWSAssertDebug(successHandler);
|
|
|
|
OWSAssertDebug(failureHandler);
|
|
|
|
OWSAssertDebug(failureHandler);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TSOutgoingMessage *message = messageSend.message;
|
|
|
|
|
|
|
|
SignalRecipient *recipient = messageSend.recipient;
|
|
|
|
|
|
|
|
|
|
|
|
OWSLogInfo(@"sending to recipient: %@, failed with error.", recipient.uniqueId);
|
|
|
|
OWSLogInfo(@"sending to recipient: %@, failed with error.", recipient.uniqueId);
|
|
|
|
|
|
|
|
|
|
|
|
void (^retrySend)(void) = ^void() {
|
|
|
|
void (^retrySend)(void) = ^void() {
|
|
|
|
if (remainingAttempts <= 0) {
|
|
|
|
if (messageSend.remainingAttempts <= 0) {
|
|
|
|
// Since we've already repeatedly failed to send to the messaging API,
|
|
|
|
// Since we've already repeatedly failed to send to the messaging API,
|
|
|
|
// it's unlikely that repeating the whole process will succeed.
|
|
|
|
// it's unlikely that repeating the whole process will succeed.
|
|
|
|
[responseError setIsRetryable:NO];
|
|
|
|
[responseError setIsRetryable:NO];
|
|
|
@ -1043,19 +1055,15 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
|
|
|
|
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
|
OWSLogDebug(@"Retrying: %@", message.debugDescription);
|
|
|
|
OWSLogDebug(@"Retrying: %@", message.debugDescription);
|
|
|
|
[self sendMessageToService:message
|
|
|
|
// TODO: Should this use sendMessageToRecipient or sendMessageToService?
|
|
|
|
recipient:recipient
|
|
|
|
[self sendMessageToRecipient:messageSend success:successHandler failure:failureHandler];
|
|
|
|
thread:thread
|
|
|
|
|
|
|
|
attempts:remainingAttempts
|
|
|
|
|
|
|
|
useWebsocketIfAvailable:NO
|
|
|
|
|
|
|
|
success:successHandler
|
|
|
|
|
|
|
|
failure:failureHandler];
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void (^handle404)(void) = ^{
|
|
|
|
void (^handle404)(void) = ^{
|
|
|
|
OWSLogWarn(@"Unregistered recipient: %@", recipient.uniqueId);
|
|
|
|
OWSLogWarn(@"Unregistered recipient: %@", recipient.uniqueId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TSThread *_Nullable thread = messageSend.thread;
|
|
|
|
OWSAssertDebug(thread);
|
|
|
|
OWSAssertDebug(thread);
|
|
|
|
|
|
|
|
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
|
dispatch_async([OWSDispatch sendingQueue], ^{
|
|
|
@ -1207,17 +1215,19 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
OWSOutgoingSentMessageTranscript *sentMessageTranscript =
|
|
|
|
OWSOutgoingSentMessageTranscript *sentMessageTranscript =
|
|
|
|
[[OWSOutgoingSentMessageTranscript alloc] initWithOutgoingMessage:message];
|
|
|
|
[[OWSOutgoingSentMessageTranscript alloc] initWithOutgoingMessage:message];
|
|
|
|
|
|
|
|
|
|
|
|
NSString *recipientId = [TSAccountManager localNumber];
|
|
|
|
NSString *recipientId = self.tsAccountManager.localNumber;
|
|
|
|
__block SignalRecipient *recipient;
|
|
|
|
__block SignalRecipient *recipient;
|
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
recipient = [SignalRecipient markRecipientAsRegisteredAndGet:recipientId transaction:transaction];
|
|
|
|
recipient = [SignalRecipient markRecipientAsRegisteredAndGet:recipientId transaction:transaction];
|
|
|
|
}];
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self sendMessageToService:sentMessageTranscript
|
|
|
|
OWSMessageSend *messageSend = [[OWSMessageSend alloc] initWithMessage:sentMessageTranscript
|
|
|
|
recipient:recipient
|
|
|
|
|
|
|
|
thread:message.thread
|
|
|
|
thread:message.thread
|
|
|
|
attempts:OWSMessageSenderRetryAttempts
|
|
|
|
recipient:recipient
|
|
|
|
useWebsocketIfAvailable:YES
|
|
|
|
udManager:self.udManager
|
|
|
|
|
|
|
|
localNumber:self.tsAccountManager.localNumber];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[self sendMessageToRecipient:messageSend
|
|
|
|
success:^{
|
|
|
|
success:^{
|
|
|
|
OWSLogInfo(@"Successfully sent sync transcript.");
|
|
|
|
OWSLogInfo(@"Successfully sent sync transcript.");
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -1231,20 +1241,25 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
}];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSArray<NSDictionary *> *)deviceMessages:(TSOutgoingMessage *)message recipient:(SignalRecipient *)recipient
|
|
|
|
// NOTE: This method uses exceptions for control flow.
|
|
|
|
|
|
|
|
- (NSArray<NSDictionary *> *)deviceMessagesForMessageSendUnsafe:(OWSMessageSend *)messageSend
|
|
|
|
{
|
|
|
|
{
|
|
|
|
OWSAssertDebug(message);
|
|
|
|
OWSAssertDebug(messageSend.message);
|
|
|
|
OWSAssertDebug(recipient);
|
|
|
|
OWSAssertDebug(messageSend.recipient);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TSOutgoingMessage *message = messageSend.message;
|
|
|
|
|
|
|
|
SignalRecipient *recipient = messageSend.recipient;
|
|
|
|
|
|
|
|
|
|
|
|
NSMutableArray *messagesArray = [NSMutableArray arrayWithCapacity:recipient.devices.count];
|
|
|
|
NSMutableArray *messagesArray = [NSMutableArray arrayWithCapacity:recipient.devices.count];
|
|
|
|
|
|
|
|
|
|
|
|
NSData *_Nullable plainText = [message buildPlainTextData:recipient];
|
|
|
|
NSData *_Nullable plainText = [messageSend.message buildPlainTextData:messageSend.recipient];
|
|
|
|
if (!plainText) {
|
|
|
|
if (!plainText) {
|
|
|
|
OWSRaiseException(InvalidMessageException, @"Failed to build message proto");
|
|
|
|
OWSRaiseException(InvalidMessageException, @"Failed to build message proto");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
OWSLogDebug(@"built message: %@ plainTextData.length: %lu", [message class], (unsigned long)plainText.length);
|
|
|
|
OWSLogDebug(
|
|
|
|
|
|
|
|
@"built message: %@ plainTextData.length: %lu", [messageSend.message class], (unsigned long)plainText.length);
|
|
|
|
|
|
|
|
|
|
|
|
for (NSNumber *deviceNumber in recipient.devices) {
|
|
|
|
for (NSNumber *deviceNumber in messageSend.recipient.devices) {
|
|
|
|
@try {
|
|
|
|
@try {
|
|
|
|
__block NSDictionary *messageDict;
|
|
|
|
__block NSDictionary *messageDict;
|
|
|
|
__block NSException *encryptionException;
|
|
|
|
__block NSException *encryptionException;
|
|
|
@ -1252,7 +1267,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
@try {
|
|
|
|
@try {
|
|
|
|
messageDict = [self encryptedMessageWithPlaintext:plainText
|
|
|
|
messageDict = [self encryptedMessageWithPlaintext:plainText
|
|
|
|
recipient:recipient
|
|
|
|
recipient:messageSend.recipient
|
|
|
|
deviceId:deviceNumber
|
|
|
|
deviceId:deviceNumber
|
|
|
|
keyingStorage:self.primaryStorage
|
|
|
|
keyingStorage:self.primaryStorage
|
|
|
|
isSilent:message.isSilent
|
|
|
|
isSilent:message.isSilent
|
|
|
@ -1412,23 +1427,24 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)saveGroupMessage:(TSOutgoingMessage *)message inThread:(TSThread *)thread
|
|
|
|
// TODO: Huh?
|
|
|
|
{
|
|
|
|
//- (void)saveGroupMessage:(TSOutgoingMessage *)message inThread:(TSThread *)thread
|
|
|
|
if (message.groupMetaMessage == TSGroupMetaMessageDeliver) {
|
|
|
|
//{
|
|
|
|
// TODO: Why is this necessary?
|
|
|
|
// if (message.groupMetaMessage == TSGroupMetaMessageDeliver) {
|
|
|
|
[message save];
|
|
|
|
// // TODO: Why is this necessary?
|
|
|
|
} else if (message.groupMetaMessage == TSGroupMetaMessageQuit) {
|
|
|
|
// [message save];
|
|
|
|
[[[TSInfoMessage alloc] initWithTimestamp:message.timestamp
|
|
|
|
// } else if (message.groupMetaMessage == TSGroupMetaMessageQuit) {
|
|
|
|
inThread:thread
|
|
|
|
// [[[TSInfoMessage alloc] initWithTimestamp:message.timestamp
|
|
|
|
messageType:TSInfoMessageTypeGroupQuit
|
|
|
|
// inThread:thread
|
|
|
|
customMessage:message.customMessage] save];
|
|
|
|
// messageType:TSInfoMessageTypeGroupQuit
|
|
|
|
} else {
|
|
|
|
// customMessage:message.customMessage] save];
|
|
|
|
[[[TSInfoMessage alloc] initWithTimestamp:message.timestamp
|
|
|
|
// } else {
|
|
|
|
inThread:thread
|
|
|
|
// [[[TSInfoMessage alloc] initWithTimestamp:message.timestamp
|
|
|
|
messageType:TSInfoMessageTypeGroupUpdate
|
|
|
|
// inThread:thread
|
|
|
|
customMessage:message.customMessage] save];
|
|
|
|
// messageType:TSInfoMessageTypeGroupUpdate
|
|
|
|
}
|
|
|
|
// customMessage:message.customMessage] save];
|
|
|
|
}
|
|
|
|
// }
|
|
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
// Called when the server indicates that the devices no longer exist - e.g. when the remote recipient has reinstalled.
|
|
|
|
// Called when the server indicates that the devices no longer exist - e.g. when the remote recipient has reinstalled.
|
|
|
|
- (void)handleStaleDevicesWithResponseJson:(NSDictionary *)responseJson
|
|
|
|
- (void)handleStaleDevicesWithResponseJson:(NSDictionary *)responseJson
|
|
|
|