1137 lines
49 KiB
Objective-C
1137 lines
49 KiB
Objective-C
//
|
|
// Copyright 2017 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
#import "TSOutgoingMessage.h"
|
|
#import "AppReadiness.h"
|
|
#import "OWSOutgoingSyncMessage.h"
|
|
#import "ProtoUtils.h"
|
|
#import "TSAttachmentStream.h"
|
|
#import "TSContactThread.h"
|
|
#import "TSGroupThread.h"
|
|
#import "TSQuotedMessage.h"
|
|
#import <SignalCoreKit/NSDate+OWS.h>
|
|
#import <SignalCoreKit/NSString+OWS.h>
|
|
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024;
|
|
|
|
NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRecipientAll";
|
|
|
|
NSString *NSStringForOutgoingMessageState(TSOutgoingMessageState value)
|
|
{
|
|
switch (value) {
|
|
case TSOutgoingMessageStateSending:
|
|
return @"TSOutgoingMessageStateSending";
|
|
case TSOutgoingMessageStateFailed:
|
|
return @"TSOutgoingMessageStateFailed";
|
|
case TSOutgoingMessageStateSent_OBSOLETE:
|
|
return @"TSOutgoingMessageStateSent_OBSOLETE";
|
|
case TSOutgoingMessageStateDelivered_OBSOLETE:
|
|
return @"TSOutgoingMessageStateDelivered_OBSOLETE";
|
|
case TSOutgoingMessageStateSent:
|
|
return @"TSOutgoingMessageStateSent";
|
|
case TSOutgoingMessageStatePending:
|
|
return @"TSOutgoingMessageStatePending";
|
|
}
|
|
}
|
|
|
|
NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientState value)
|
|
{
|
|
switch (value) {
|
|
case OWSOutgoingMessageRecipientStateFailed:
|
|
return @"OWSOutgoingMessageRecipientStateFailed";
|
|
case OWSOutgoingMessageRecipientStateSending:
|
|
return @"OWSOutgoingMessageRecipientStateSending";
|
|
case OWSOutgoingMessageRecipientStateSkipped:
|
|
return @"OWSOutgoingMessageRecipientStateSkipped";
|
|
case OWSOutgoingMessageRecipientStateSent:
|
|
return @"OWSOutgoingMessageRecipientStateSent";
|
|
case OWSOutgoingMessageRecipientStatePending:
|
|
return @"OWSOutgoingMessageRecipientStatePending";
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
@interface TSMessage (Private)
|
|
|
|
- (void)removeAllAttachmentsWithTransaction:(SDSAnyWriteTransaction *)transaction;
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@interface TSOutgoingMessageRecipientState ()
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@implementation TSOutgoingMessageRecipientState
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
NSUInteger const TSOutgoingMessageSchemaVersion = 1;
|
|
|
|
@interface TSOutgoingMessage ()
|
|
|
|
@property (atomic) BOOL hasSyncedTranscript;
|
|
@property (atomic, nullable) NSString *customMessage;
|
|
@property (atomic, nullable) NSString *mostRecentFailureText;
|
|
@property (atomic) BOOL wasNotCreatedLocally;
|
|
@property (atomic) TSGroupMetaMessage groupMetaMessage;
|
|
@property (nonatomic, readonly) NSUInteger outgoingMessageSchemaVersion;
|
|
|
|
@property (nonatomic, readonly) TSOutgoingMessageState legacyMessageState;
|
|
@property (nonatomic, readonly) BOOL legacyWasDelivered;
|
|
@property (nonatomic, readonly) BOOL hasLegacyMessageState;
|
|
|
|
// This property is only intended to be used by GRDB queries.
|
|
@property (nonatomic, readonly) TSOutgoingMessageState storedMessageState;
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@implementation TSOutgoingMessage
|
|
|
|
// --- CODE GENERATION MARKER
|
|
|
|
// This snippet is generated by /Scripts/sds_codegen/sds_generate.py. Do not manually edit it, instead run
|
|
// `sds_codegen.sh`.
|
|
|
|
// clang-format off
|
|
|
|
- (instancetype)initWithGrdbId:(int64_t)grdbId
|
|
uniqueId:(NSString *)uniqueId
|
|
receivedAtTimestamp:(uint64_t)receivedAtTimestamp
|
|
sortId:(uint64_t)sortId
|
|
timestamp:(uint64_t)timestamp
|
|
uniqueThreadId:(NSString *)uniqueThreadId
|
|
attachmentIds:(NSArray<NSString *> *)attachmentIds
|
|
body:(nullable NSString *)body
|
|
bodyRanges:(nullable MessageBodyRanges *)bodyRanges
|
|
contactShare:(nullable OWSContact *)contactShare
|
|
editState:(TSEditState)editState
|
|
expireStartedAt:(uint64_t)expireStartedAt
|
|
expiresAt:(uint64_t)expiresAt
|
|
expiresInSeconds:(unsigned int)expiresInSeconds
|
|
giftBadge:(nullable OWSGiftBadge *)giftBadge
|
|
isGroupStoryReply:(BOOL)isGroupStoryReply
|
|
isViewOnceComplete:(BOOL)isViewOnceComplete
|
|
isViewOnceMessage:(BOOL)isViewOnceMessage
|
|
linkPreview:(nullable OWSLinkPreview *)linkPreview
|
|
messageSticker:(nullable MessageSticker *)messageSticker
|
|
quotedMessage:(nullable TSQuotedMessage *)quotedMessage
|
|
storedShouldStartExpireTimer:(BOOL)storedShouldStartExpireTimer
|
|
storyAuthorUuidString:(nullable NSString *)storyAuthorUuidString
|
|
storyReactionEmoji:(nullable NSString *)storyReactionEmoji
|
|
storyTimestamp:(nullable NSNumber *)storyTimestamp
|
|
wasRemotelyDeleted:(BOOL)wasRemotelyDeleted
|
|
customMessage:(nullable NSString *)customMessage
|
|
groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage
|
|
hasLegacyMessageState:(BOOL)hasLegacyMessageState
|
|
hasSyncedTranscript:(BOOL)hasSyncedTranscript
|
|
isVoiceMessage:(BOOL)isVoiceMessage
|
|
legacyMessageState:(TSOutgoingMessageState)legacyMessageState
|
|
legacyWasDelivered:(BOOL)legacyWasDelivered
|
|
mostRecentFailureText:(nullable NSString *)mostRecentFailureText
|
|
recipientAddressStates:(nullable NSDictionary<SignalServiceAddress *,TSOutgoingMessageRecipientState *> *)recipientAddressStates
|
|
storedMessageState:(TSOutgoingMessageState)storedMessageState
|
|
wasNotCreatedLocally:(BOOL)wasNotCreatedLocally
|
|
{
|
|
self = [super initWithGrdbId:grdbId
|
|
uniqueId:uniqueId
|
|
receivedAtTimestamp:receivedAtTimestamp
|
|
sortId:sortId
|
|
timestamp:timestamp
|
|
uniqueThreadId:uniqueThreadId
|
|
attachmentIds:attachmentIds
|
|
body:body
|
|
bodyRanges:bodyRanges
|
|
contactShare:contactShare
|
|
editState:editState
|
|
expireStartedAt:expireStartedAt
|
|
expiresAt:expiresAt
|
|
expiresInSeconds:expiresInSeconds
|
|
giftBadge:giftBadge
|
|
isGroupStoryReply:isGroupStoryReply
|
|
isViewOnceComplete:isViewOnceComplete
|
|
isViewOnceMessage:isViewOnceMessage
|
|
linkPreview:linkPreview
|
|
messageSticker:messageSticker
|
|
quotedMessage:quotedMessage
|
|
storedShouldStartExpireTimer:storedShouldStartExpireTimer
|
|
storyAuthorUuidString:storyAuthorUuidString
|
|
storyReactionEmoji:storyReactionEmoji
|
|
storyTimestamp:storyTimestamp
|
|
wasRemotelyDeleted:wasRemotelyDeleted];
|
|
|
|
if (!self) {
|
|
return self;
|
|
}
|
|
|
|
_customMessage = customMessage;
|
|
_groupMetaMessage = groupMetaMessage;
|
|
_hasLegacyMessageState = hasLegacyMessageState;
|
|
_hasSyncedTranscript = hasSyncedTranscript;
|
|
_isVoiceMessage = isVoiceMessage;
|
|
_legacyMessageState = legacyMessageState;
|
|
_legacyWasDelivered = legacyWasDelivered;
|
|
_mostRecentFailureText = mostRecentFailureText;
|
|
_recipientAddressStates = recipientAddressStates;
|
|
_storedMessageState = storedMessageState;
|
|
_wasNotCreatedLocally = wasNotCreatedLocally;
|
|
|
|
return self;
|
|
}
|
|
|
|
// clang-format on
|
|
|
|
// --- CODE GENERATION MARKER
|
|
|
|
- (nullable instancetype)initWithCoder:(NSCoder *)coder
|
|
{
|
|
self = [super initWithCoder:coder];
|
|
|
|
if (self) {
|
|
#ifndef TESTABLE_BUILD
|
|
OWSAssertDebug(self.outgoingMessageSchemaVersion >= 1);
|
|
#endif
|
|
|
|
_outgoingMessageSchemaVersion = TSOutgoingMessageSchemaVersion;
|
|
}
|
|
|
|
|
|
return self;
|
|
}
|
|
|
|
+ (instancetype)outgoingMessageInThread:(TSThread *)thread
|
|
messageBody:(nullable NSString *)body
|
|
attachmentId:(nullable NSString *)attachmentId
|
|
{
|
|
return [self outgoingMessageInThread:thread
|
|
messageBody:body
|
|
attachmentId:attachmentId
|
|
expiresInSeconds:0
|
|
quotedMessage:nil
|
|
linkPreview:nil
|
|
messageSticker:nil];
|
|
}
|
|
|
|
+ (instancetype)outgoingMessageInThread:(TSThread *)thread
|
|
messageBody:(nullable NSString *)body
|
|
attachmentId:(nullable NSString *)attachmentId
|
|
expiresInSeconds:(uint32_t)expiresInSeconds
|
|
{
|
|
return [self outgoingMessageInThread:thread
|
|
messageBody:body
|
|
attachmentId:attachmentId
|
|
expiresInSeconds:expiresInSeconds
|
|
quotedMessage:nil
|
|
linkPreview:nil
|
|
messageSticker:nil];
|
|
}
|
|
|
|
+ (instancetype)outgoingMessageInThread:(TSThread *)thread
|
|
messageBody:(nullable NSString *)body
|
|
attachmentId:(nullable NSString *)attachmentId
|
|
expiresInSeconds:(uint32_t)expiresInSeconds
|
|
quotedMessage:(nullable TSQuotedMessage *)quotedMessage
|
|
linkPreview:(nullable OWSLinkPreview *)linkPreview
|
|
messageSticker:(nullable MessageSticker *)messageSticker
|
|
{
|
|
NSMutableArray<NSString *> *attachmentIds = [NSMutableArray new];
|
|
if (attachmentId) {
|
|
[attachmentIds addObject:attachmentId];
|
|
}
|
|
|
|
TSOutgoingMessageBuilder *builder = [TSOutgoingMessageBuilder outgoingMessageBuilderWithThread:thread];
|
|
builder.messageBody = body;
|
|
builder.attachmentIds = attachmentIds;
|
|
builder.expiresInSeconds = expiresInSeconds;
|
|
builder.quotedMessage = quotedMessage;
|
|
builder.linkPreview = linkPreview;
|
|
builder.messageSticker = messageSticker;
|
|
return [builder buildWithSneakyTransaction];
|
|
}
|
|
|
|
- (instancetype)initOutgoingMessageWithBuilder:(TSOutgoingMessageBuilder *)outgoingMessageBuilder
|
|
transaction:(SDSAnyReadTransaction *)transaction
|
|
{
|
|
self = [super initMessageWithBuilder:outgoingMessageBuilder];
|
|
if (!self) {
|
|
return self;
|
|
}
|
|
|
|
_hasSyncedTranscript = NO;
|
|
|
|
TSThread *thread = outgoingMessageBuilder.thread;
|
|
TSGroupMetaMessage groupMetaMessage = outgoingMessageBuilder.groupMetaMessage;
|
|
|
|
if ([thread isKindOfClass:TSGroupThread.class]) {
|
|
// Unless specified, we assume group messages are "Delivery" i.e. normal messages.
|
|
if (groupMetaMessage == TSGroupMetaMessageUnspecified) {
|
|
_groupMetaMessage = TSGroupMetaMessageDeliver;
|
|
} else {
|
|
_groupMetaMessage = groupMetaMessage;
|
|
}
|
|
} else {
|
|
OWSAssertDebug(groupMetaMessage == TSGroupMetaMessageUnspecified);
|
|
// Specifying a group meta message only makes sense for Group threads
|
|
_groupMetaMessage = TSGroupMetaMessageUnspecified;
|
|
}
|
|
|
|
_isVoiceMessage = outgoingMessageBuilder.isVoiceMessage;
|
|
|
|
// New outgoing messages should immediately determine their
|
|
// recipient list from current thread state.
|
|
NSMutableSet<SignalServiceAddress *> *recipientAddresses = [NSMutableSet new];
|
|
if ([self isKindOfClass:[OWSOutgoingSyncMessage class]]) {
|
|
// 1. Sync messages should only be sent to linked devices.
|
|
SignalServiceAddress *localAddress = [TSAccountManagerObjcBridge localAciAddressWith:transaction];
|
|
OWSAssertDebug(localAddress);
|
|
[recipientAddresses addObject:localAddress];
|
|
} else {
|
|
// 2. Most messages should only be sent to the current members of the group.
|
|
[recipientAddresses addObjectsFromArray:[thread recipientAddressesWithTransaction:transaction]];
|
|
// 3. V2 group updates should also be sent to pending members of the group
|
|
// whose clients support Groups v2.
|
|
if (outgoingMessageBuilder.additionalRecipients) {
|
|
[recipientAddresses addObjectsFromArray:outgoingMessageBuilder.additionalRecipients];
|
|
}
|
|
}
|
|
|
|
NSMutableDictionary<SignalServiceAddress *, TSOutgoingMessageRecipientState *> *recipientAddressStates =
|
|
[NSMutableDictionary new];
|
|
for (SignalServiceAddress *recipientAddress in recipientAddresses) {
|
|
if (!recipientAddress.isValid) {
|
|
OWSFailDebug(@"Ignoring invalid address.");
|
|
continue;
|
|
}
|
|
TSOutgoingMessageRecipientState *recipientState = [TSOutgoingMessageRecipientState new];
|
|
recipientState.state = [outgoingMessageBuilder.skippedRecipients containsObject:recipientAddress]
|
|
? OWSOutgoingMessageRecipientStateSkipped
|
|
: OWSOutgoingMessageRecipientStateSending;
|
|
recipientAddressStates[recipientAddress] = recipientState;
|
|
}
|
|
self.recipientAddressStates = [recipientAddressStates copy];
|
|
_outgoingMessageSchemaVersion = TSOutgoingMessageSchemaVersion;
|
|
|
|
_changeActionsProtoData = outgoingMessageBuilder.changeActionsProtoData;
|
|
|
|
return self;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (TSOutgoingMessageState)messageState
|
|
{
|
|
TSOutgoingMessageState newMessageState =
|
|
[TSOutgoingMessage messageStateForRecipientStates:self.recipientAddressStates.allValues];
|
|
if (self.hasLegacyMessageState) {
|
|
if (newMessageState == TSOutgoingMessageStateSent || self.legacyMessageState == TSOutgoingMessageStateSent) {
|
|
return TSOutgoingMessageStateSent;
|
|
}
|
|
}
|
|
return newMessageState;
|
|
}
|
|
|
|
- (BOOL)wasDeliveredToAnyRecipient
|
|
{
|
|
if (self.deliveredRecipientAddresses.count > 0) {
|
|
return YES;
|
|
}
|
|
return (self.hasLegacyMessageState && self.legacyWasDelivered && self.messageState == TSOutgoingMessageStateSent);
|
|
}
|
|
|
|
- (BOOL)wasSentToAnyRecipient
|
|
{
|
|
if (self.sentRecipientAddresses.count > 0) {
|
|
return YES;
|
|
}
|
|
return (self.hasLegacyMessageState && self.messageState == TSOutgoingMessageStateSent);
|
|
}
|
|
|
|
+ (TSOutgoingMessageState)messageStateForRecipientStates:(NSArray<TSOutgoingMessageRecipientState *> *)recipientStates
|
|
{
|
|
OWSAssertDebug(recipientStates);
|
|
|
|
// If there are any "sending" recipients, consider this message "sending".
|
|
// If there are any "pending" recipients, consider this message "pending".
|
|
BOOL hasFailed = NO;
|
|
for (TSOutgoingMessageRecipientState *recipientState in recipientStates) {
|
|
if (recipientState.state == OWSOutgoingMessageRecipientStateSending) {
|
|
return TSOutgoingMessageStateSending;
|
|
} else if (recipientState.state == OWSOutgoingMessageRecipientStatePending) {
|
|
return TSOutgoingMessageStatePending;
|
|
} else if (recipientState.state == OWSOutgoingMessageRecipientStateFailed) {
|
|
hasFailed = YES;
|
|
}
|
|
}
|
|
|
|
// If there are any "failed" recipients, consider this message "failed".
|
|
if (hasFailed) {
|
|
return TSOutgoingMessageStateFailed;
|
|
}
|
|
|
|
// Otherwise, consider the message "sent".
|
|
//
|
|
// NOTE: This includes messages with no recipients.
|
|
return TSOutgoingMessageStateSent;
|
|
}
|
|
|
|
- (BOOL)shouldBeSaved
|
|
{
|
|
if (!super.shouldBeSaved) {
|
|
return NO;
|
|
}
|
|
if (self.groupMetaMessage == TSGroupMetaMessageDeliver || self.groupMetaMessage == TSGroupMetaMessageUnspecified) {
|
|
return YES;
|
|
}
|
|
|
|
// There's no need to save this message, since it's not displayed to the user.
|
|
//
|
|
// Should we find a need to save this in the future, we need to exclude any non-serializable properties.
|
|
return NO;
|
|
}
|
|
|
|
- (void)anyWillInsertWithTransaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
[super anyWillInsertWithTransaction:transaction];
|
|
|
|
_storedMessageState = self.messageState;
|
|
}
|
|
|
|
- (void)anyDidInsertWithTransaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
[super anyDidInsertWithTransaction:transaction];
|
|
[self markMessageSendLogEntryCompleteIfNeededWithTx:transaction];
|
|
}
|
|
|
|
- (void)anyWillUpdateWithTransaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
[super anyWillUpdateWithTransaction:transaction];
|
|
|
|
_storedMessageState = self.messageState;
|
|
}
|
|
|
|
- (void)anyDidUpdateWithTransaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
[super anyDidUpdateWithTransaction:transaction];
|
|
[self markMessageSendLogEntryCompleteIfNeededWithTx:transaction];
|
|
}
|
|
|
|
// This method will be called after every insert and update, so it needs
|
|
// to be cheap.
|
|
- (BOOL)shouldStartExpireTimer
|
|
{
|
|
if (self.hasPerConversationExpirationStarted) {
|
|
// Expiration already started.
|
|
return YES;
|
|
} else if (!self.hasPerConversationExpiration) {
|
|
return NO;
|
|
} else if (!super.shouldStartExpireTimer) {
|
|
return NO;
|
|
}
|
|
|
|
// It's not clear if we should wait until _all_ recipients have reached "sent or later"
|
|
// (which could never occur if one group member is unregistered) or only wait until
|
|
// the first recipient has reached "sent or later" (which could cause partially delivered
|
|
// messages to expire). For now, we'll do the latter.
|
|
//
|
|
// TODO: Revisit this decision.
|
|
return self.messageState == TSOutgoingMessageStateSent;
|
|
}
|
|
|
|
- (BOOL)isOnline
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)isUrgent
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (OWSInteractionType)interactionType
|
|
{
|
|
return OWSInteractionType_OutgoingMessage;
|
|
}
|
|
|
|
// MCR: Check what calls this method and if it needs to be changed.
|
|
- (NSArray<SignalServiceAddress *> *)recipientAddresses
|
|
{
|
|
return self.recipientAddressStates.allKeys;
|
|
}
|
|
|
|
- (NSArray<SignalServiceAddress *> *)sendingRecipientAddresses
|
|
{
|
|
NSMutableArray<SignalServiceAddress *> *result = [NSMutableArray new];
|
|
for (SignalServiceAddress *recipientAddress in self.recipientAddressStates) {
|
|
TSOutgoingMessageRecipientState *recipientState = self.recipientAddressStates[recipientAddress];
|
|
if (recipientState.state == OWSOutgoingMessageRecipientStateSending
|
|
|| recipientState.state == OWSOutgoingMessageRecipientStatePending) {
|
|
[result addObject:recipientAddress];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
- (NSArray<SignalServiceAddress *> *)sentRecipientAddresses
|
|
{
|
|
NSMutableArray<SignalServiceAddress *> *result = [NSMutableArray new];
|
|
for (SignalServiceAddress *recipientAddress in self.recipientAddressStates) {
|
|
TSOutgoingMessageRecipientState *recipientState = self.recipientAddressStates[recipientAddress];
|
|
if (recipientState.state == OWSOutgoingMessageRecipientStateSent) {
|
|
[result addObject:recipientAddress];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
- (NSArray<SignalServiceAddress *> *)deliveredRecipientAddresses
|
|
{
|
|
NSMutableArray<SignalServiceAddress *> *result = [NSMutableArray new];
|
|
for (SignalServiceAddress *recipientAddress in self.recipientAddressStates) {
|
|
TSOutgoingMessageRecipientState *recipientState = self.recipientAddressStates[recipientAddress];
|
|
if (recipientState.deliveryTimestamp != nil) {
|
|
[result addObject:recipientAddress];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
- (NSArray<SignalServiceAddress *> *)readRecipientAddresses
|
|
{
|
|
NSMutableArray<SignalServiceAddress *> *result = [NSMutableArray new];
|
|
for (SignalServiceAddress *recipientAddress in self.recipientAddressStates) {
|
|
TSOutgoingMessageRecipientState *recipientState = self.recipientAddressStates[recipientAddress];
|
|
if (recipientState.readTimestamp != nil) {
|
|
[result addObject:recipientAddress];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
- (NSArray<SignalServiceAddress *> *)viewedRecipientAddresses
|
|
{
|
|
NSMutableArray<SignalServiceAddress *> *result = [NSMutableArray new];
|
|
for (SignalServiceAddress *recipientAddress in self.recipientAddressStates) {
|
|
TSOutgoingMessageRecipientState *recipientState = self.recipientAddressStates[recipientAddress];
|
|
if (recipientState.viewedTimestamp != nil) {
|
|
[result addObject:recipientAddress];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
- (NSUInteger)sentRecipientsCount
|
|
{
|
|
return [self.recipientAddressStates
|
|
.allValues filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
|
|
TSOutgoingMessageRecipientState *recipientState,
|
|
NSDictionary<NSString *, id> *_Nullable bindings) {
|
|
return recipientState.state == OWSOutgoingMessageRecipientStateSent;
|
|
}]].count;
|
|
}
|
|
|
|
// MCR: Check what calls this and if it needs to be changed.
|
|
- (nullable TSOutgoingMessageRecipientState *)recipientStateForAddress:(SignalServiceAddress *)address
|
|
{
|
|
OWSAssertDebug(address.isValid);
|
|
|
|
TSOutgoingMessageRecipientState *_Nullable result = self.recipientAddressStates[address];
|
|
OWSAssertDebug(result);
|
|
return [result copy];
|
|
}
|
|
|
|
#pragma mark - Update With... Methods
|
|
|
|
- (void)updateWithSendingError:(NSError *)error transaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
OWSAssertDebug(error);
|
|
[self
|
|
anyUpdateOutgoingMessageWithTransaction:transaction
|
|
block:^(TSOutgoingMessage *message) {
|
|
// Mark any "sending" recipients as "failed."
|
|
for (TSOutgoingMessageRecipientState *recipientState in message
|
|
.recipientAddressStates.allValues) {
|
|
if (recipientState.state == OWSOutgoingMessageRecipientStateSending) {
|
|
recipientState.state = OWSOutgoingMessageRecipientStateFailed;
|
|
}
|
|
}
|
|
[message setMostRecentFailureText:error.userErrorDescription];
|
|
}];
|
|
}
|
|
|
|
- (void)updateWithAllSendingRecipientsMarkedAsFailedWithTransaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
OWSAssertDebug(transaction);
|
|
|
|
[self
|
|
anyUpdateOutgoingMessageWithTransaction:transaction
|
|
block:^(TSOutgoingMessage *message) {
|
|
// Mark any "sending" recipients as "failed."
|
|
for (TSOutgoingMessageRecipientState *recipientState in message
|
|
.recipientAddressStates.allValues) {
|
|
if (recipientState.state == OWSOutgoingMessageRecipientStateSending) {
|
|
recipientState.state = OWSOutgoingMessageRecipientStateFailed;
|
|
}
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (BOOL)hasFailedRecipients
|
|
{
|
|
for (TSOutgoingMessageRecipientState *recipientState in self.recipientAddressStates.allValues) {
|
|
if (recipientState.state == OWSOutgoingMessageRecipientStateFailed) {
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (void)updateAllUnsentRecipientsAsSendingWithTransaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
OWSAssertDebug(transaction);
|
|
|
|
[self
|
|
anyUpdateOutgoingMessageWithTransaction:transaction
|
|
block:^(TSOutgoingMessage *message) {
|
|
// Mark any "failed" recipients as "sending."
|
|
for (TSOutgoingMessageRecipientState *recipientState in message
|
|
.recipientAddressStates.allValues) {
|
|
if (recipientState.state == OWSOutgoingMessageRecipientStateFailed) {
|
|
recipientState.state = OWSOutgoingMessageRecipientStateSending;
|
|
}
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)updateWithHasSyncedTranscript:(BOOL)hasSyncedTranscript transaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
[self anyUpdateOutgoingMessageWithTransaction:transaction
|
|
block:^(TSOutgoingMessage *message) {
|
|
[message setHasSyncedTranscript:hasSyncedTranscript];
|
|
}];
|
|
}
|
|
|
|
- (void)updateWithSentRecipient:(ServiceIdObjC *)serviceId
|
|
wasSentByUD:(BOOL)wasSentByUD
|
|
transaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
OWSAssertDebug(serviceId);
|
|
OWSAssertDebug(transaction);
|
|
|
|
SignalServiceAddress *recipientAddress = [[SignalServiceAddress alloc] initWithServiceIdObjC:serviceId];
|
|
[self updateWithSentRecipientAddress:recipientAddress wasSentByUD:wasSentByUD transaction:transaction];
|
|
}
|
|
|
|
- (void)updateWithSentRecipientAddress:(SignalServiceAddress *)recipientAddress
|
|
wasSentByUD:(BOOL)wasSentByUD
|
|
transaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
OWSAssertDebug(recipientAddress);
|
|
OWSAssertDebug(transaction);
|
|
[self anyUpdateOutgoingMessageWithTransaction:transaction
|
|
block:^(TSOutgoingMessage *message) {
|
|
TSOutgoingMessageRecipientState *_Nullable recipientState
|
|
= message.recipientAddressStates[recipientAddress];
|
|
if (!recipientState) {
|
|
OWSFailDebug(
|
|
@"Missing recipient state for recipient: %@", recipientAddress);
|
|
return;
|
|
}
|
|
recipientState.state = OWSOutgoingMessageRecipientStateSent;
|
|
recipientState.wasSentByUD = wasSentByUD;
|
|
recipientState.errorCode = nil;
|
|
}];
|
|
}
|
|
|
|
- (void)updateWithSkippedRecipient:(SignalServiceAddress *)recipientAddress
|
|
transaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
OWSAssertDebug(recipientAddress.isValid);
|
|
OWSAssertDebug(transaction);
|
|
|
|
[self anyUpdateOutgoingMessageWithTransaction:transaction
|
|
block:^(TSOutgoingMessage *message) {
|
|
TSOutgoingMessageRecipientState *_Nullable recipientState
|
|
= message.recipientAddressStates[recipientAddress];
|
|
if (!recipientState) {
|
|
OWSFailDebug(
|
|
@"Missing recipient state for recipient: %@", recipientAddress);
|
|
return;
|
|
}
|
|
recipientState.state = OWSOutgoingMessageRecipientStateSkipped;
|
|
}];
|
|
}
|
|
|
|
- (void)updateWithFailedRecipient:(SignalServiceAddress *)recipientAddress
|
|
error:(NSError *)error
|
|
transaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
OWSAssertDebug(recipientAddress.isValid);
|
|
OWSAssertDebug(transaction);
|
|
|
|
OWSLogWarn(@"Send to recipient failed, address: %@, timestamp: %llu, error: %@ (isRetryable: %d)",
|
|
recipientAddress,
|
|
self.timestamp,
|
|
error,
|
|
error.isRetryable);
|
|
|
|
[self
|
|
anyUpdateOutgoingMessageWithTransaction:transaction
|
|
block:^(TSOutgoingMessage *message) {
|
|
TSOutgoingMessageRecipientState *_Nullable recipientState
|
|
= message.recipientAddressStates[recipientAddress];
|
|
if (!recipientState) {
|
|
OWSFailDebug(
|
|
@"Missing recipient state for recipient: %@", recipientAddress);
|
|
return;
|
|
}
|
|
|
|
if (error.isRetryable
|
|
&& recipientState.state == OWSOutgoingMessageRecipientStateSending) {
|
|
// For retryable errors, we can just set the error code and leave the
|
|
// state set as Sending
|
|
} else if ([SpamChallengeRequiredError isSpamChallengeRequiredError:error]
|
|
|| [SpamChallengeResolvedError isSpamChallengeResolvedError:error]) {
|
|
recipientState.state = OWSOutgoingMessageRecipientStatePending;
|
|
} else {
|
|
recipientState.state = OWSOutgoingMessageRecipientStateFailed;
|
|
}
|
|
recipientState.errorCode = @(error.code);
|
|
}];
|
|
}
|
|
|
|
- (void)updateRecipientsFromNonLocalDevice:
|
|
(NSDictionary<SignalServiceAddress *, TSOutgoingMessageRecipientState *> *)recipientStates
|
|
isSentUpdate:(BOOL)isSentUpdate
|
|
transaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
OWSAssertDebug(transaction);
|
|
|
|
[self anyUpdateOutgoingMessageWithTransaction:transaction
|
|
block:^(TSOutgoingMessage *message) {
|
|
if (recipientStates.count > 0) {
|
|
// If we have specific recipient info from the transcript,
|
|
// build a new recipient state map.
|
|
|
|
|
|
if (isSentUpdate) {
|
|
// If this is a "sent update", make sure that:
|
|
//
|
|
// a) "Sent updates" should never remove any recipients. We end
|
|
// up with the
|
|
// union of the existing and new recipients.
|
|
// b) "Sent updates" should never downgrade the "recipient
|
|
// state" for any
|
|
// recipients. Prefer existing "recipient state"; "sent
|
|
// updates" only add new recipients at the "sent" state.
|
|
//
|
|
// Therefore we retain all existing entries in the recipient
|
|
// state map.
|
|
NSMutableDictionary<SignalServiceAddress *,
|
|
TSOutgoingMessageRecipientState *> *recipientAddressStates
|
|
= recipientStates.mutableCopy;
|
|
[recipientAddressStates
|
|
addEntriesFromDictionary:self.recipientAddressStates];
|
|
[message setRecipientAddressStates:recipientAddressStates];
|
|
} else {
|
|
[message setRecipientAddressStates:recipientStates];
|
|
}
|
|
} else {
|
|
// Otherwise assume this is a legacy message before UD was
|
|
// introduced, and mark any "sending" recipient as "sent". Note
|
|
// that this will apply to non-legacy messages with no recipients.
|
|
for (TSOutgoingMessageRecipientState *recipientState in message
|
|
.recipientAddressStates.allValues) {
|
|
if (recipientState.state
|
|
== OWSOutgoingMessageRecipientStateSending) {
|
|
recipientState.state = OWSOutgoingMessageRecipientStateSent;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isSentUpdate) {
|
|
[message setWasNotCreatedLocally:YES];
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)updateWithRecipientAddressStates:
|
|
(nullable NSDictionary<SignalServiceAddress *, TSOutgoingMessageRecipientState *> *)recipientAddressStates
|
|
transaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
[self anyUpdateOutgoingMessageWithTransaction:transaction
|
|
block:^(TSOutgoingMessage *message) {
|
|
message.recipientAddressStates = [recipientAddressStates copy];
|
|
}];
|
|
}
|
|
|
|
#ifdef TESTABLE_BUILD
|
|
- (void)updateWithFakeMessageState:(TSOutgoingMessageState)messageState
|
|
transaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
OWSAssertDebug(transaction);
|
|
[self anyUpdateOutgoingMessageWithTransaction:transaction
|
|
block:^(TSOutgoingMessage *message) {
|
|
for (TSOutgoingMessageRecipientState *recipientState in message
|
|
.recipientAddressStates.allValues) {
|
|
switch (messageState) {
|
|
case TSOutgoingMessageStateSending:
|
|
recipientState.state
|
|
= OWSOutgoingMessageRecipientStateSending;
|
|
break;
|
|
case TSOutgoingMessageStateFailed:
|
|
recipientState.state
|
|
= OWSOutgoingMessageRecipientStateFailed;
|
|
break;
|
|
case TSOutgoingMessageStateSent:
|
|
recipientState.state = OWSOutgoingMessageRecipientStateSent;
|
|
break;
|
|
case TSOutgoingMessageStatePending:
|
|
recipientState.state
|
|
= OWSOutgoingMessageRecipientStatePending;
|
|
break;
|
|
default:
|
|
OWSFailDebug(@"unexpected message state.");
|
|
break;
|
|
}
|
|
}
|
|
}];
|
|
}
|
|
#endif
|
|
|
|
#pragma mark -
|
|
|
|
- (nullable SSKProtoDataMessageBuilder *)dataMessageBuilderWithThread:(TSThread *)thread
|
|
transaction:(SDSAnyReadTransaction *)transaction
|
|
{
|
|
OWSAssertDebug(thread);
|
|
|
|
SSKProtoDataMessageBuilder *builder = [SSKProtoDataMessage builder];
|
|
[builder setTimestamp:self.timestamp];
|
|
|
|
NSUInteger requiredProtocolVersion = SSKProtoDataMessageProtocolVersionInitial;
|
|
|
|
if (self.isViewOnceMessage) {
|
|
[builder setIsViewOnce:YES];
|
|
requiredProtocolVersion = SSKProtoDataMessageProtocolVersionViewOnceVideo;
|
|
}
|
|
|
|
if ([self.body lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= kOversizeTextMessageSizeThreshold) {
|
|
[builder setBody:self.body];
|
|
} else {
|
|
OWSFailDebug(@"message body length too long.");
|
|
NSString *truncatedBody = [self.body copy];
|
|
while ([truncatedBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > kOversizeTextMessageSizeThreshold) {
|
|
OWSLogError(@"truncating body which is too long: %lu",
|
|
(unsigned long)[truncatedBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
|
|
truncatedBody = [truncatedBody substringToIndex:truncatedBody.length / 2];
|
|
}
|
|
[builder setBody:truncatedBody];
|
|
}
|
|
|
|
NSArray<SSKProtoBodyRange *> *bodyRanges =
|
|
[self.bodyRanges toProtoBodyRangesWithBodyLength:(NSInteger)self.body.length];
|
|
if (bodyRanges.count > 0) {
|
|
[builder setBodyRanges:bodyRanges];
|
|
|
|
if (requiredProtocolVersion < SSKProtoDataMessageProtocolVersionMentions) {
|
|
requiredProtocolVersion = SSKProtoDataMessageProtocolVersionMentions;
|
|
}
|
|
}
|
|
|
|
// Story Context
|
|
if (self.storyTimestamp && self.storyAuthorUuidString) {
|
|
if (self.storyReactionEmoji) {
|
|
SSKProtoDataMessageReactionBuilder *reactionBuilder =
|
|
[SSKProtoDataMessageReaction builderWithEmoji:self.storyReactionEmoji
|
|
timestamp:self.storyTimestamp.unsignedLongLongValue];
|
|
// ACI TODO: Use `serviceIdString` to populate this value.
|
|
[reactionBuilder setTargetAuthorAci:self.storyAuthorUuidString];
|
|
|
|
NSError *error;
|
|
SSKProtoDataMessageReaction *_Nullable reaction = [reactionBuilder buildAndReturnError:&error];
|
|
if (error || !reaction) {
|
|
OWSFailDebug(@"Could not build story reaction protobuf: %@.", error);
|
|
} else {
|
|
[builder setReaction:reaction];
|
|
|
|
if (requiredProtocolVersion < SSKProtoDataMessageProtocolVersionReactions) {
|
|
requiredProtocolVersion = SSKProtoDataMessageProtocolVersionReactions;
|
|
}
|
|
}
|
|
}
|
|
|
|
SSKProtoDataMessageStoryContextBuilder *storyContextBuilder = [SSKProtoDataMessageStoryContext builder];
|
|
// ACI TODO: Use `serviceIdString` to populate this value.
|
|
[storyContextBuilder setAuthorAci:self.storyAuthorUuidString];
|
|
[storyContextBuilder setSentTimestamp:self.storyTimestamp.unsignedLongLongValue];
|
|
|
|
[builder setStoryContext:[storyContextBuilder buildInfallibly]];
|
|
}
|
|
|
|
[builder setExpireTimer:self.expiresInSeconds];
|
|
|
|
// Group Messages
|
|
if ([thread isKindOfClass:[TSGroupThread class]]) {
|
|
TSGroupThread *groupThread = (TSGroupThread *)thread;
|
|
OutgoingGroupProtoResult result;
|
|
switch (groupThread.groupModel.groupsVersion) {
|
|
case GroupsVersionV1:
|
|
OWSLogError(@"[GV1] Cannot build data message for V1 group!");
|
|
result = OutgoingGroupProtoResult_Error;
|
|
break;
|
|
case GroupsVersionV2:
|
|
result = [self addGroupsV2ToDataMessageBuilder:builder groupThread:groupThread tx:transaction];
|
|
break;
|
|
}
|
|
switch (result) {
|
|
case OutgoingGroupProtoResult_Error:
|
|
return nil;
|
|
case OutgoingGroupProtoResult_AddedWithoutGroupAvatar:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Message Attachments
|
|
NSArray<SSKProtoAttachmentPointer *> *attachments = [self buildProtosForBodyAttachmentsWithTx:transaction];
|
|
[builder setAttachments:attachments];
|
|
|
|
// Quoted Reply
|
|
SSKProtoDataMessageQuoteBuilder *_Nullable quotedMessageBuilder =
|
|
[self quotedMessageBuilderWithTransaction:transaction];
|
|
if (quotedMessageBuilder) {
|
|
NSError *error;
|
|
SSKProtoDataMessageQuote *_Nullable quoteProto = [quotedMessageBuilder buildAndReturnError:&error];
|
|
if (error || !quoteProto) {
|
|
OWSFailDebug(@"could not build protobuf: %@.", error);
|
|
return nil;
|
|
}
|
|
[builder setQuote:quoteProto];
|
|
|
|
if (quoteProto.bodyRanges.count > 0) {
|
|
if (requiredProtocolVersion < SSKProtoDataMessageProtocolVersionMentions) {
|
|
requiredProtocolVersion = SSKProtoDataMessageProtocolVersionMentions;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Contact Share
|
|
if (self.contactShare) {
|
|
SSKProtoDataMessageContact *_Nullable contactProto =
|
|
[self.contactShare buildProtoWithParentMessage:self tx:transaction];
|
|
if (contactProto) {
|
|
[builder addContact:contactProto];
|
|
} else {
|
|
OWSFailDebug(@"contactProto was unexpectedly nil");
|
|
}
|
|
}
|
|
|
|
// Link Preview
|
|
if (self.linkPreview) {
|
|
NSError *error;
|
|
SSKProtoPreview *_Nullable previewProto = [self buildLinkPreviewProtoWithLinkPreview:self.linkPreview
|
|
tx:transaction
|
|
error:&error];
|
|
if (error || !previewProto) {
|
|
OWSFailDebug(@"Could not build link preview protobuf: %@.", error);
|
|
} else {
|
|
[builder addPreview:previewProto];
|
|
}
|
|
}
|
|
|
|
// Sticker
|
|
if (self.messageSticker) {
|
|
SSKProtoAttachmentPointer *_Nullable attachmentProto = [self buildProtoForStickerAttachmentWithTx:transaction];
|
|
if (!attachmentProto) {
|
|
OWSFailDebug(@"Could not build sticker attachment protobuf.");
|
|
} else {
|
|
SSKProtoDataMessageStickerBuilder *stickerBuilder =
|
|
[SSKProtoDataMessageSticker builderWithPackID:self.messageSticker.packId
|
|
packKey:self.messageSticker.packKey
|
|
stickerID:self.messageSticker.stickerId
|
|
data:attachmentProto];
|
|
if (self.messageSticker.emoji.length > 0) {
|
|
[stickerBuilder setEmoji:self.messageSticker.emoji];
|
|
}
|
|
|
|
NSError *error;
|
|
SSKProtoDataMessageSticker *_Nullable stickerProto = [stickerBuilder buildAndReturnError:&error];
|
|
if (error || !stickerProto) {
|
|
OWSFailDebug(@"Could not build sticker protobuf: %@.", error);
|
|
} else {
|
|
[builder setSticker:stickerProto];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gift badge
|
|
if (self.giftBadge) {
|
|
SSKProtoDataMessageGiftBadgeBuilder *giftBadgeBuilder = [SSKProtoDataMessageGiftBadge builder];
|
|
[giftBadgeBuilder setReceiptCredentialPresentation:self.giftBadge.redemptionCredential];
|
|
[builder setGiftBadge:[giftBadgeBuilder buildInfallibly]];
|
|
}
|
|
|
|
[builder setRequiredProtocolVersion:(uint32_t)requiredProtocolVersion];
|
|
return builder;
|
|
}
|
|
|
|
- (nullable SSKProtoDataMessageQuoteBuilder *)quotedMessageBuilderWithTransaction:(SDSAnyReadTransaction *)transaction
|
|
{
|
|
if (!self.quotedMessage) {
|
|
return nil;
|
|
}
|
|
TSQuotedMessage *quotedMessage = self.quotedMessage;
|
|
|
|
if (!quotedMessage.timestampValue) {
|
|
return nil;
|
|
}
|
|
uint64_t timestamp = quotedMessage.timestampValue.unsignedLongLongValue;
|
|
|
|
SSKProtoDataMessageQuoteBuilder *quoteBuilder = [SSKProtoDataMessageQuote builderWithId:timestamp];
|
|
|
|
if (quotedMessage.authorAddress.aciString) {
|
|
[quoteBuilder setAuthorAci:quotedMessage.authorAddress.aciString];
|
|
} else {
|
|
OWSFailDebug(@"It should be impossible to quote a message without a UUID");
|
|
return nil;
|
|
}
|
|
|
|
BOOL hasQuotedText = NO;
|
|
BOOL hasQuotedAttachment = NO;
|
|
BOOL hasQuotedGiftBadge = NO;
|
|
|
|
if (quotedMessage.body.length > 0) {
|
|
hasQuotedText = YES;
|
|
[quoteBuilder setText:quotedMessage.body];
|
|
|
|
NSArray<SSKProtoBodyRange *> *bodyRanges =
|
|
[self.quotedMessage.bodyRanges toProtoBodyRangesWithBodyLength:(NSInteger)self.quotedMessage.body.length];
|
|
if (bodyRanges.count > 0) {
|
|
[quoteBuilder setBodyRanges:bodyRanges];
|
|
}
|
|
}
|
|
|
|
SSKProtoDataMessageQuoteQuotedAttachment *attachmentProto =
|
|
[self buildQuotedMessageAttachmentProtoWithTx:transaction];
|
|
if (attachmentProto) {
|
|
[quoteBuilder addAttachments:attachmentProto];
|
|
hasQuotedAttachment = YES;
|
|
}
|
|
|
|
if (quotedMessage.isGiftBadge) {
|
|
[quoteBuilder setType:SSKProtoDataMessageQuoteTypeGiftBadge];
|
|
hasQuotedGiftBadge = YES;
|
|
}
|
|
|
|
if (hasQuotedText || hasQuotedAttachment || hasQuotedGiftBadge) {
|
|
return quoteBuilder;
|
|
} else {
|
|
OWSFailDebug(@"Invalid quoted message data.");
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
// recipientId is nil when building "sent" sync messages for messages sent to groups.
|
|
- (nullable SSKProtoDataMessage *)buildDataMessage:(TSThread *)thread transaction:(SDSAnyReadTransaction *)transaction
|
|
{
|
|
OWSAssertDebug(thread);
|
|
OWSAssertDebug([thread.uniqueId isEqualToString:self.uniqueThreadId]);
|
|
SSKProtoDataMessageBuilder *_Nullable builder = [self dataMessageBuilderWithThread:thread transaction:transaction];
|
|
if (!builder) {
|
|
OWSFailDebug(@"could not build protobuf.");
|
|
return nil;
|
|
}
|
|
|
|
[ProtoUtils addLocalProfileKeyIfNecessary:thread dataMessageBuilder:builder transaction:transaction];
|
|
|
|
NSError *error;
|
|
SSKProtoDataMessage *_Nullable dataProto = [builder buildAndReturnError:&error];
|
|
if (error || !dataProto) {
|
|
OWSFailDebug(@"could not build protobuf: %@", error);
|
|
return nil;
|
|
}
|
|
return dataProto;
|
|
}
|
|
|
|
- (nullable SSKProtoContentBuilder *)contentBuilderWithThread:(TSThread *)thread
|
|
transaction:(SDSAnyReadTransaction *)transaction
|
|
{
|
|
SSKProtoDataMessage *_Nullable dataMessage = [self buildDataMessage:thread transaction:transaction];
|
|
if (!dataMessage) {
|
|
return nil;
|
|
}
|
|
|
|
SSKProtoContentBuilder *contentBuilder = [SSKProtoContent builder];
|
|
[contentBuilder setDataMessage:dataMessage];
|
|
return contentBuilder;
|
|
}
|
|
|
|
- (nullable NSData *)buildPlainTextData:(TSThread *)thread transaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
SSKProtoContentBuilder *_Nullable contentBuilder = [self contentBuilderWithThread:thread transaction:transaction];
|
|
if (!contentBuilder) {
|
|
OWSFailDebug(@"could not build protobuf.");
|
|
return nil;
|
|
}
|
|
|
|
[contentBuilder setPniSignatureMessage:[self buildPniSignatureMessageIfNeededWithTransaction:transaction]];
|
|
|
|
NSError *error;
|
|
NSData *_Nullable contentData = [contentBuilder buildSerializedDataAndReturnError:&error];
|
|
if (error || !contentData) {
|
|
OWSFailDebug(@"could not serialize protobuf: %@", error);
|
|
return nil;
|
|
}
|
|
return contentData;
|
|
}
|
|
|
|
- (BOOL)shouldSyncTranscript
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (nullable OWSOutgoingSyncMessage *)buildTranscriptSyncMessageWithLocalThread:(TSThread *)localThread
|
|
transaction:(SDSAnyWriteTransaction *)transaction
|
|
{
|
|
OWSAssertDebug(self.shouldSyncTranscript);
|
|
|
|
TSThread *messageThread = [self threadWithTx:transaction];
|
|
if (messageThread == nil) {
|
|
return nil;
|
|
}
|
|
|
|
return [[OWSOutgoingSentMessageTranscript alloc] initWithLocalThread:localThread
|
|
messageThread:messageThread
|
|
outgoingMessage:self
|
|
isRecipientUpdate:self.hasSyncedTranscript
|
|
transaction:transaction];
|
|
}
|
|
|
|
- (NSString *)statusDescription
|
|
{
|
|
NSMutableString *result = [NSMutableString new];
|
|
[result appendFormat:@"[status: %@\n", NSStringForOutgoingMessageState(self.messageState)];
|
|
for (SignalServiceAddress *address in self.recipientAddressStates) {
|
|
TSOutgoingMessageRecipientState *recipientState = self.recipientAddressStates[address];
|
|
[result appendFormat:@", %@: %@\n", address, NSStringForOutgoingMessageRecipientState(recipientState.state)];
|
|
}
|
|
[result appendString:@"]"];
|
|
return [result copy];
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|