From 4f19d03bdc4a4e645e1787cb18ba82edaeb30abe Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 20 Feb 2019 10:11:09 -0500 Subject: [PATCH] Send 'sent update' sync messages. --- .../src/Devices/OWSRecordTranscriptJob.h | 2 - .../src/Devices/OWSRecordTranscriptJob.m | 14 +-- .../OWSOutgoingSentUpdateMessageTranscript.h | 25 +++++ .../OWSOutgoingSentUpdateMessageTranscript.m | 100 ++++++++++++++++++ .../Messages/Interactions/TSOutgoingMessage.h | 5 + .../Messages/Interactions/TSOutgoingMessage.m | 7 +- .../src/Messages/OWSMessageSender.m | 80 +++++++++++--- 7 files changed, 203 insertions(+), 30 deletions(-) create mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentUpdateMessageTranscript.h create mode 100644 SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentUpdateMessageTranscript.m diff --git a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.h b/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.h index 016c47ef9..7969a58ee 100644 --- a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.h +++ b/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.h @@ -19,8 +19,6 @@ NS_ASSUME_NONNULL_BEGIN NSArray *attachmentStreams))attachmentHandler transaction:(YapDatabaseReadWriteTransaction *)transaction; -+ (BOOL)areSentUpdatesEnabled; - + (void)processSentUpdateTranscript:(SSKProtoSyncMessageSentUpdate *)sentUpdate transaction:(YapDatabaseReadWriteTransaction *)transaction; diff --git a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m b/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m index d8c3425ef..bb871ae5e 100644 --- a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m +++ b/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m @@ -67,13 +67,6 @@ NS_ASSUME_NONNULL_BEGIN OWSAssertDebug(transcript); OWSAssertDebug(transaction); - if (self.dataMessage.group) { - _thread = [TSGroupThread getOrCreateThreadWithGroupId:_dataMessage.group.id transaction:transaction]; - } else { - _thread = [TSContactThread getOrCreateThreadWithContactId:_recipientId transaction:transaction]; - } - - OWSLogInfo(@"Recording transcript in thread: %@ timestamp: %llu", transcript.thread.uniqueId, transcript.timestamp); if (transcript.isEndSessionMessage) { @@ -183,18 +176,13 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - -+ (BOOL)areSentUpdatesEnabled -{ - return NO; -} - + (void)processSentUpdateTranscript:(SSKProtoSyncMessageSentUpdate *)sentUpdate transaction:(YapDatabaseReadWriteTransaction *)transaction { OWSAssertDebug(sentUpdate); OWSAssertDebug(transaction); - if (!self.areSentUpdatesEnabled) { + if (!AreSentUpdatesEnabled()) { OWSFailDebug(@"Ignoring 'sent update' transcript; disabled."); return; } diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentUpdateMessageTranscript.h b/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentUpdateMessageTranscript.h new file mode 100644 index 000000000..7678eb12b --- /dev/null +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentUpdateMessageTranscript.h @@ -0,0 +1,25 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +#import "OWSOutgoingSyncMessage.h" + +NS_ASSUME_NONNULL_BEGIN + +@class TSOutgoingMessage; + +/** + * Notifies your other registered devices (if you have any) that you've sent a message. + * This way the message you just sent can appear on all your devices. + */ +@interface OWSOutgoingSentUpdateMessageTranscript : OWSOutgoingSyncMessage + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithOutgoingMessage:(TSOutgoingMessage *)message + transaction:(YapDatabaseReadTransaction *)transaction NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentUpdateMessageTranscript.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentUpdateMessageTranscript.m new file mode 100644 index 000000000..2ec1e8390 --- /dev/null +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentUpdateMessageTranscript.m @@ -0,0 +1,100 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +#import "OWSOutgoingSentUpdateMessageTranscript.h" +#import "TSGroupThread.h" +#import "TSOutgoingMessage.h" +#import "TSThread.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface TSOutgoingMessage (OWSOutgoingSentMessageTranscript) + +/** + * Normally this is private, but we need to embed this + * data structure within our own. + * + * recipientId is nil when building "sent" sync messages for messages + * sent to groups. + */ +- (nullable SSKProtoDataMessage *)buildDataMessage:(NSString *_Nullable)recipientId; + +@end + +@interface OWSOutgoingSentUpdateMessageTranscript () + +@property (nonatomic, readonly) TSOutgoingMessage *message; +@property (nonatomic, readonly) TSGroupThread *groupThread; + +@end + +@implementation OWSOutgoingSentUpdateMessageTranscript + +- (instancetype)initWithOutgoingMessage:(TSOutgoingMessage *)message + transaction:(YapDatabaseReadTransaction *)transaction +{ + self = [super init]; + + if (!self) { + return self; + } + + _message = message; + _groupThread = (TSGroupThread *)[message threadWithTransaction:transaction]; + + return self; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)coder +{ + return [super initWithCoder:coder]; +} + +- (nullable SSKProtoSyncMessageBuilder *)syncMessageBuilder +{ + SSKProtoSyncMessageSentUpdateBuilder *sentBuilder = + [SSKProtoSyncMessageSentUpdate builderWithGroupID:self.groupThread.groupModel.groupId + timestamp:self.message.timestamp]; + + for (NSString *recipientId in self.message.sentRecipientIds) { + TSOutgoingMessageRecipientState *_Nullable recipientState = + [self.message recipientStateForRecipientId:recipientId]; + if (!recipientState) { + OWSFailDebug(@"missing recipient state for: %@", recipientId); + continue; + } + if (recipientState.state != OWSOutgoingMessageRecipientStateSent) { + OWSFailDebug(@"unexpected recipient state for: %@", recipientId); + continue; + } + + NSError *error; + SSKProtoSyncMessageSentUpdateUnidentifiedDeliveryStatusBuilder *statusBuilder = + [SSKProtoSyncMessageSentUpdateUnidentifiedDeliveryStatus builderWithDestination:recipientId]; + [statusBuilder setUnidentified:recipientState.wasSentByUD]; + SSKProtoSyncMessageSentUpdateUnidentifiedDeliveryStatus *_Nullable status = + [statusBuilder buildAndReturnError:&error]; + if (error || !status) { + OWSFailDebug(@"Couldn't build UD status proto: %@", error); + continue; + } + [sentBuilder addUnidentifiedStatus:status]; + } + + NSError *error; + SSKProtoSyncMessageSentUpdate *_Nullable sentUpdateProto = [sentBuilder buildAndReturnError:&error]; + if (error || !sentUpdateProto) { + OWSFailDebug(@"could not build protobuf: %@", error); + return nil; + } + + SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; + [syncMessageBuilder setSentUpdate:sentUpdateProto]; + return syncMessageBuilder; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h index f75a997cc..80995e2b2 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h @@ -6,6 +6,11 @@ NS_ASSUME_NONNULL_BEGIN +// Feature flag. +// +// TODO: Remove. +BOOL AreSentUpdatesEnabled(void); + typedef NS_ENUM(NSInteger, TSOutgoingMessageState) { // The message is either: // a) Enqueued for sending. diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index c2768391e..988e9b5d7 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -23,6 +23,11 @@ NS_ASSUME_NONNULL_BEGIN +BOOL AreSentUpdatesEnabled(void) +{ + return NO; +} + NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRecipientAll"; NSString *NSStringForOutgoingMessageState(TSOutgoingMessageState value) @@ -1104,7 +1109,7 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt - (BOOL)shouldSyncTranscript { - return !self.hasSyncedTranscript; + return YES; } - (NSString *)statusDescription diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 303b2b74e..e05625603 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -18,6 +18,7 @@ #import "OWSMessageServiceParams.h" #import "OWSOperation.h" #import "OWSOutgoingSentMessageTranscript.h" +#import "OWSOutgoingSentUpdateMessageTranscript.h" #import "OWSOutgoingSyncMessage.h" #import "OWSPrimaryStorage+PreKeyStore.h" #import "OWSPrimaryStorage+SignedPreKeyStore.h" @@ -1391,20 +1392,27 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; 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]; + if (message.hasSyncedTranscript) { + if (!AreSentUpdatesEnabled()) { + return success(); + } + [self sendSyncUpdateTranscriptForMessage:message + success:^{ + success(); + } + failure:failure]; + } else { + [self sendSyncTranscriptForMessage:message + success:^{ + [self.dbConnection + readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [message updateWithHasSyncedTranscript:YES transaction:transaction]; + }]; + + success(); + } + failure:failure]; + } } - (void)sendSyncTranscriptForMessage:(TSOutgoingMessage *)message @@ -1439,6 +1447,50 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; [self sendMessageToRecipient:messageSend]; } +- (void)sendSyncUpdateTranscriptForMessage:(TSOutgoingMessage *)message + success:(void (^)(void))success + failure:(RetryableFailureHandler)failure +{ + NSString *recipientId = self.tsAccountManager.localNumber; + + __block OWSOutgoingSentUpdateMessageTranscript *transcript; + __block BOOL isGroupThread = NO; + __block SignalRecipient *recipient; + [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + isGroupThread = message.thread.isGroupThread; + + if (isGroupThread) { + recipient = [SignalRecipient markRecipientAsRegisteredAndGet:recipientId transaction:transaction]; + transcript = [[OWSOutgoingSentUpdateMessageTranscript alloc] initWithOutgoingMessage:message + transaction:transaction]; + } + }]; + + if (!isGroupThread) { + // We only send "sent update" transcripts for group messages. + return success(); + } + + OWSMessageSend *messageSend = [[OWSMessageSend alloc] initWithMessage:transcript + thread:message.thread + recipient:recipient + senderCertificate:nil + udAccess:nil + localNumber:self.tsAccountManager.localNumber + success:^{ + OWSLogInfo(@"Successfully sent 'sent update' sync transcript."); + + success(); + } + failure:^(NSError *error) { + OWSLogInfo( + @"Failed to send 'sent update' sync transcript: %@ (isRetryable: %d)", error, [error isRetryable]); + + failure(error); + }]; + [self sendMessageToRecipient:messageSend]; +} + - (NSArray *)throws_deviceMessagesForMessageSend:(OWSMessageSend *)messageSend { OWSAssertDebug(messageSend.message);