Untangle receipts, transcripts & typing indicators logic

pull/178/head
nielsandriesse 5 years ago
parent 9ae54412bd
commit 4098b9638c

@ -2827,5 +2827,5 @@
"Please make sure the Session ID you entered is correct and try again." = "Please make sure the Session ID you entered is correct and try again."; "Please make sure the Session ID you entered is correct and try again." = "Please make sure the Session ID you entered is correct and try again.";
"Device Linking Failed" = "Device Linking Failed"; "Device Linking Failed" = "Device Linking Failed";
"Please check your internet connection and try again" = "Please check your internet connection and try again"; "Please check your internet connection and try again" = "Please check your internet connection and try again";
"Authorization Device Link" = "Authorization Device Link"; "Authorizing Device Link" = "Authorizing Device Link";
"Please wait while the device link is created. This can take up to a minute." = "Please wait while the device link is created. This can take up to a minute."; "Please wait while the device link is created. This can take up to a minute." = "Please wait while the device link is created. This can take up to a minute.";

@ -182,7 +182,8 @@ typedef void (^BuildOutgoingMessageCompletionBlock)(TSOutgoingMessage *savedMess
// Loki: If we're not friends then always set the message to a friend request message. // Loki: If we're not friends then always set the message to a friend request message.
// If we're friends then the assumption is that we have the other user's pre key bundle. // If we're friends then the assumption is that we have the other user's pre key bundle.
NSString *messageClassAsString = (thread.isContactFriend || thread.isGroupThread) ? @"TSOutgoingMessage" : @"LKFriendRequestMessage"; BOOL isNoteToSelf = [LKSessionMetaProtocol isMessageNoteToSelf:thread];
NSString *messageClassAsString = (thread.isContactFriend || thread.isGroupThread || isNoteToSelf) ? @"TSOutgoingMessage" : @"LKFriendRequestMessage";
Class messageClass = NSClassFromString(messageClassAsString); Class messageClass = NSClassFromString(messageClassAsString);
TSOutgoingMessage *message = TSOutgoingMessage *message =

@ -216,7 +216,7 @@ ConversationColorName const kConversationColorName_Default = ConversationColorNa
if (!IsNoteToSelfEnabled()) { if (!IsNoteToSelfEnabled()) {
return NO; return NO;
} }
return [LKSessionProtocol isMessageNoteToSelf:self]; return [LKSessionMetaProtocol isMessageNoteToSelf:self];
} }
#pragma mark - To be subclassed. #pragma mark - To be subclassed.

@ -22,5 +22,6 @@
#pragma mark Settings #pragma mark Settings
- (BOOL)shouldBeSaved { return NO; } - (BOOL)shouldBeSaved { return NO; }
- (uint)ttl { return (uint)[LKTTLUtilities getTTLFor:LKMessageTypeSessionRequest]; } - (uint)ttl { return (uint)[LKTTLUtilities getTTLFor:LKMessageTypeSessionRequest]; }
- (BOOL)shouldSyncTranscript { return NO; }
@end @end

@ -21,7 +21,7 @@ public final class FriendRequestProtocol : NSObject {
// Friend requests have nothing to do with groups, so if this isn't a contact thread the input bar should be enabled // Friend requests have nothing to do with groups, so if this isn't a contact thread the input bar should be enabled
guard let thread = thread as? TSContactThread else { return true } guard let thread = thread as? TSContactThread else { return true }
// If this is a note to self, the input bar should be enabled // If this is a note to self, the input bar should be enabled
if SessionProtocol.isMessageNoteToSelf(thread) { return true } if SessionMetaProtocol.isMessageNoteToSelf(thread) { return true }
let contactID = thread.contactIdentifier() let contactID = thread.contactIdentifier()
var linkedDeviceThreads: Set<TSContactThread> = [] var linkedDeviceThreads: Set<TSContactThread> = []
storage.dbReadConnection.read { transaction in storage.dbReadConnection.read { transaction in
@ -40,7 +40,7 @@ public final class FriendRequestProtocol : NSObject {
// Friend requests have nothing to do with groups, so if this isn't a contact thread the attachment button should be enabled // Friend requests have nothing to do with groups, so if this isn't a contact thread the attachment button should be enabled
guard let thread = thread as? TSContactThread else { return true } guard let thread = thread as? TSContactThread else { return true }
// If this is a note to self, the attachment button should be enabled // If this is a note to self, the attachment button should be enabled
if SessionProtocol.isMessageNoteToSelf(thread) { return true } if SessionMetaProtocol.isMessageNoteToSelf(thread) { return true }
let contactID = thread.contactIdentifier() let contactID = thread.contactIdentifier()
var linkedDeviceThreads: Set<TSContactThread> = [] var linkedDeviceThreads: Set<TSContactThread> = []
storage.dbReadConnection.read { transaction in storage.dbReadConnection.read { transaction in

@ -9,8 +9,8 @@ import PromiseKit
// Document the expected cases for everything. // Document the expected cases for everything.
// Express those cases in tests. // Express those cases in tests.
@objc(LKSessionProtocol) @objc(LKSessionMetaProtocol)
public final class SessionProtocol : NSObject { public final class SessionMetaProtocol : NSObject {
internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() }
@ -58,15 +58,6 @@ public final class SessionProtocol : NSObject {
} }
// MARK: Note to Self // MARK: Note to Self
// BEHAVIOR NOTE: OWSMessageSender.sendMessageToService:senderCertificate:success:failure: aborts early and just sends
// a sync message instead if the message it's supposed to send is considered a note to self (INCLUDING linked devices).
// BEHAVIOR NOTE: OWSMessageSender.sendMessage: aborts early and does nothing if the message is target at
// the current user (EXCLUDING linked devices).
// BEHAVIOR NOTE: OWSMessageSender.handleMessageSentLocally:success:failure: doesn't send a sync transcript if the message
// that was sent is considered a note to self (INCLUDING linked devices) but it does then mark the message as read.
// TODO: Check that the behaviors described above make sense
@objc(isMessageNoteToSelf:) @objc(isMessageNoteToSelf:)
public static func isMessageNoteToSelf(_ thread: TSThread) -> Bool { public static func isMessageNoteToSelf(_ thread: TSThread) -> Bool {
guard let thread = thread as? TSContactThread else { return false } guard let thread = thread as? TSContactThread else { return false }
@ -77,23 +68,12 @@ public final class SessionProtocol : NSObject {
return isNoteToSelf return isNoteToSelf
} }
@objc(isMessageNoteToSelf:inThread:)
public static func isMessageNoteToSelf(_ message: TSOutgoingMessage, in thread: TSThread) -> Bool {
guard let thread = thread as? TSContactThread, !(message is OWSOutgoingSyncMessage) && !(message is DeviceLinkMessage) else { return false }
var isNoteToSelf = false
storage.dbReadConnection.read { transaction in
isNoteToSelf = LokiDatabaseUtilities.isUserLinkedDevice(thread.contactIdentifier(), transaction: transaction)
}
return isNoteToSelf
}
// MARK: Transcripts // MARK: Transcripts
@objc(shouldSendTranscriptForMessage:in:) @objc(shouldSendTranscriptForMessage:in:)
public static func shouldSendTranscript(for message: TSOutgoingMessage, in thread: TSThread) -> Bool { public static func shouldSendTranscript(for message: TSOutgoingMessage, in thread: TSThread) -> Bool {
let isNoteToSelf = isMessageNoteToSelf(message, in: thread)
let isOpenGroupMessage = (thread as? TSGroupThread)?.isPublicChat == true let isOpenGroupMessage = (thread as? TSGroupThread)?.isPublicChat == true
let wouldSignalRequireTranscript = (AreRecipientUpdatesEnabled() || !message.hasSyncedTranscript) let wouldSignalRequireTranscript = (AreRecipientUpdatesEnabled() || !message.hasSyncedTranscript)
return wouldSignalRequireTranscript && !isNoteToSelf && !isOpenGroupMessage && !(message is DeviceLinkMessage) return wouldSignalRequireTranscript && !isOpenGroupMessage
} }
// MARK: Typing Indicators // MARK: Typing Indicators
@ -101,43 +81,16 @@ public final class SessionProtocol : NSObject {
/// send them if certain conditions are met. /// send them if certain conditions are met.
@objc(shouldSendTypingIndicatorForThread:) @objc(shouldSendTypingIndicatorForThread:)
public static func shouldSendTypingIndicator(for thread: TSThread) -> Bool { public static func shouldSendTypingIndicator(for thread: TSThread) -> Bool {
return !thread.isGroupThread() && !isMessageNoteToSelf(thread) return thread.friendRequestStatus == .friends && !thread.isGroupThread()
} }
// MARK: Receipts // MARK: Receipts
// Used from OWSReadReceiptManager
@objc(shouldSendReadReceiptForThread:)
public static func shouldSendReadReceipt(for thread: TSThread) -> Bool {
return !isMessageNoteToSelf(thread) && !thread.isGroupThread()
}
// TODO: Not sure how these two relate
// EDIT: I think the one below is used to block delivery receipts. That means that
// right now we do send delivery receipts in note to self, but not read receipts. Other than that their behavior should
// be identical. Should we just not send any kind of receipt in note to self?
// Used from OWSOutgoingReceiptManager
@objc(shouldSendReceiptForThread:) @objc(shouldSendReceiptForThread:)
public static func shouldSendReceipt(for thread: TSThread) -> Bool { public static func shouldSendReceipt(for thread: TSThread) -> Bool {
return thread.friendRequestStatus == .friends && !thread.isGroupThread() return thread.friendRequestStatus == .friends && !thread.isGroupThread()
} }
// MARK: - Receiving // MARK: - Receiving
// When a message comes in, OWSMessageManager does things in this order:
// 1. Checks if the message is a friend request from before restoration and ignores it if so
// 2. Handles friend request acceptance if needed
// 3. Checks if the message is a duplicate sync message and ignores it if so
// 4. Handles pre keys if needed (this also might trigger a session reset)
// 5. Updates P2P info if the message is a P2P address message
// 6. Handle device linking requests or authorizations if needed (it now doesn't continue along the normal message handling path)
// - If the message is a data message and has the session request flag set, processing stops here
// - If the message is a data message and has the session restore flag set, processing stops here
// 7. If the message got to this point, and it has an updated profile key attached, it'll now handle the profile key
// - If the message is a closed group message, it'll now check if it needs to be ignored
// ...
// MARK: - Decryption
@objc(shouldSkipMessageDecryptResult:) @objc(shouldSkipMessageDecryptResult:)
public static func shouldSkipMessageDecryptResult(_ result: OWSMessageDecryptResult) -> Bool { public static func shouldSkipMessageDecryptResult(_ result: OWSMessageDecryptResult) -> Bool {
// Called from OWSMessageReceiver to prevent messages from even being added to the processing queue // Called from OWSMessageReceiver to prevent messages from even being added to the processing queue

@ -219,8 +219,8 @@ public final class MultiDeviceProtocol : NSObject {
} }
// Set any profile info (the device link authorization also includes the master device's profile info) // Set any profile info (the device link authorization also includes the master device's profile info)
if let dataMessage = protoContent.dataMessage { if let dataMessage = protoContent.dataMessage {
SessionProtocol.updateDisplayNameIfNeeded(for: master, using: dataMessage, appendingShortID: false, in: transaction) SessionMetaProtocol.updateDisplayNameIfNeeded(for: master, using: dataMessage, appendingShortID: false, in: transaction)
SessionProtocol.updateProfileKeyIfNeeded(for: master, using: dataMessage) SessionMetaProtocol.updateProfileKeyIfNeeded(for: master, using: dataMessage)
} }
} else { // Request } else { // Request
print("[Loki] Received a device link request from: \(hexEncodedPublicKey).") // Intentionally not `slave` print("[Loki] Received a device link request from: \(hexEncodedPublicKey).") // Intentionally not `slave`

@ -117,8 +117,8 @@ public final class SyncMessagesProtocol : NSObject {
guard let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: getUserHexEncodedPublicKey(), in: transaction) else { return } guard let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: getUserHexEncodedPublicKey(), in: transaction) else { return }
let wasSentByMasterDevice = (masterHexEncodedPublicKey == hexEncodedPublicKey) let wasSentByMasterDevice = (masterHexEncodedPublicKey == hexEncodedPublicKey)
guard wasSentByMasterDevice else { return } guard wasSentByMasterDevice else { return }
SessionProtocol.updateDisplayNameIfNeeded(for: masterHexEncodedPublicKey, using: dataMessage, appendingShortID: false, in: transaction) SessionMetaProtocol.updateDisplayNameIfNeeded(for: masterHexEncodedPublicKey, using: dataMessage, appendingShortID: false, in: transaction)
SessionProtocol.updateProfileKeyIfNeeded(for: masterHexEncodedPublicKey, using: dataMessage) SessionMetaProtocol.updateProfileKeyIfNeeded(for: masterHexEncodedPublicKey, using: dataMessage)
} }
@objc(handleClosedGroupUpdatedSyncMessageIfNeeded:using:) @objc(handleClosedGroupUpdatedSyncMessageIfNeeded:using:)

@ -441,7 +441,7 @@ NS_ASSUME_NONNULL_BEGIN
// Loki: Handle address message if needed // Loki: Handle address message if needed
/* /*
[LKSessionProtocol handleP2PAddressMessageIfNeeded:contentProto wrappedIn:envelope]; [LKSessionMetaProtocol handleP2PAddressMessageIfNeeded:contentProto wrappedIn:envelope];
*/ */
// Loki: Handle device linking message if needed // Loki: Handle device linking message if needed
@ -1296,10 +1296,10 @@ NS_ASSUME_NONNULL_BEGIN
} }
// Loki: Handle profile key update if needed // Loki: Handle profile key update if needed
[LKSessionProtocol updateProfileKeyIfNeededForHexEncodedPublicKey:senderMasterHexEncodedPublicKey using:dataMessage]; [LKSessionMetaProtocol updateProfileKeyIfNeededForHexEncodedPublicKey:senderMasterHexEncodedPublicKey using:dataMessage];
// Loki: Handle display name update if needed // Loki: Handle display name update if needed
[LKSessionProtocol updateDisplayNameIfNeededForHexEncodedPublicKey:senderMasterHexEncodedPublicKey using:dataMessage appendingShortID:NO in:transaction]; [LKSessionMetaProtocol updateDisplayNameIfNeededForHexEncodedPublicKey:senderMasterHexEncodedPublicKey using:dataMessage appendingShortID:NO in:transaction];
switch (dataMessage.group.type) { switch (dataMessage.group.type) {
case SSKProtoGroupContextTypeUpdate: { case SSKProtoGroupContextTypeUpdate: {
@ -1520,8 +1520,8 @@ NS_ASSUME_NONNULL_BEGIN
wasReceivedByUD:wasReceivedByUD]; wasReceivedByUD:wasReceivedByUD];
// TODO: Are we sure this works correctly with multi device? // TODO: Are we sure this works correctly with multi device?
[LKSessionProtocol updateDisplayNameIfNeededForHexEncodedPublicKey:incomingMessage.authorId using:dataMessage appendingShortID:YES in:transaction]; [LKSessionMetaProtocol updateDisplayNameIfNeededForHexEncodedPublicKey:incomingMessage.authorId using:dataMessage appendingShortID:YES in:transaction];
[LKSessionProtocol updateProfileKeyIfNeededForHexEncodedPublicKey:thread.contactIdentifier using:dataMessage]; [LKSessionMetaProtocol updateProfileKeyIfNeededForHexEncodedPublicKey:thread.contactIdentifier using:dataMessage];
// Loki: Parse Loki specific properties if needed // Loki: Parse Loki specific properties if needed
/* /*

@ -398,7 +398,7 @@ NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessin
OWSAssertDebug(transaction); OWSAssertDebug(transaction);
// Loki: Don't process any messages from ourself // Loki: Don't process any messages from ourself
if ([LKSessionProtocol shouldSkipMessageDecryptResult:result]) { if ([LKSessionMetaProtocol shouldSkipMessageDecryptResult:result]) {
dispatch_async(self.serialQueue, ^{ dispatch_async(self.serialQueue, ^{
completion(YES); completion(YES);
}); });

@ -500,10 +500,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
__block NSMutableSet<NSString *> *recipientIds = [NSMutableSet new]; __block NSMutableSet<NSString *> *recipientIds = [NSMutableSet new];
if ([message isKindOfClass:[OWSOutgoingSyncMessage class]]) { if ([message isKindOfClass:[OWSOutgoingSyncMessage class]]) {
recipientIds = [LKSessionProtocol getDestinationsForOutgoingSyncMessage]; recipientIds = [LKSessionMetaProtocol getDestinationsForOutgoingSyncMessage];
} else if (thread.isGroupThread) { } else if (thread.isGroupThread) {
TSGroupThread *groupThread = (TSGroupThread *)thread; TSGroupThread *groupThread = (TSGroupThread *)thread;
recipientIds = [LKSessionProtocol getDestinationsForOutgoingGroupMessage:message inThread:thread]; recipientIds = [LKSessionMetaProtocol getDestinationsForOutgoingGroupMessage:message inThread:thread];
} else if ([thread isKindOfClass:[TSContactThread class]]) { } else if ([thread isKindOfClass:[TSContactThread class]]) {
NSString *recipientContactId = ((TSContactThread *)thread).contactIdentifier; NSString *recipientContactId = ((TSContactThread *)thread).contactIdentifier;
@ -670,10 +670,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
return failureHandler(error); return failureHandler(error);
} }
// Loki: Abort early and send a sync transcript if this is a note to self // In the "self-send" special case, we ony need to send a sync message with a delivery receipt
if ([LKSessionProtocol isMessageNoteToSelf:message inThread:thread]) { // Loki: Take into account multi device
// FIXME: I think this is where the duplicate sync messages might be coming from. Signal just invokes successHandler() here. if ([LKSessionMetaProtocol isMessageNoteToSelf:thread] && !([message isKindOfClass:LKDeviceLinkMessage.class])) {
[self sendSyncTranscriptForMessage:message isRecipientUpdate:NO success:^{ } failure:^(NSError *error) { }]; // Don't mark self-sent messages as read (or sent) until the sync transcript is sent
successHandler(); successHandler();
return; return;
} }
@ -917,14 +917,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
TSOutgoingMessage *message = messageSend.message; TSOutgoingMessage *message = messageSend.message;
SignalRecipient *recipient = messageSend.recipient; SignalRecipient *recipient = messageSend.recipient;
// Loki: Ignore messages addressed to self
// TODO: Why?
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
if ([messageSend.recipient.recipientId isEqual:userHexEncodedPublicKey]) {
[LKLogger print:[NSString stringWithFormat:@"[Loki] Ignoring %@ addressed to self.", message.class]];
return messageSend.success();
}
OWSLogInfo(@"Attempting to send message: %@, timestamp: %llu, recipient: %@.", OWSLogInfo(@"Attempting to send message: %@, timestamp: %llu, recipient: %@.",
message.class, message.class,
message.timestamp, message.timestamp,
@ -1488,9 +1480,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
failure:(RetryableFailureHandler)failure failure:(RetryableFailureHandler)failure
{ {
dispatch_block_t success = ^{ dispatch_block_t success = ^{
// Loki: Handle note to self case // Don't mark self-sent messages as read (or sent) until the sync transcript is sent
BOOL isNoteToSelf = [LKSessionProtocol isMessageNoteToSelf:message inThread:message.thread]; // Loki: Take into account multi device
if (isNoteToSelf) { BOOL isNoteToSelf = [LKSessionMetaProtocol isMessageNoteToSelf:message.thread];
if (isNoteToSelf && !([message isKindOfClass:LKDeviceLinkMessage.class])) {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *recipientId in message.sendingRecipientIds) { for (NSString *recipientId in message.sendingRecipientIds) {
[message updateWithReadRecipientId:recipientId readTimestamp:message.timestamp transaction:transaction]; [message updateWithReadRecipientId:recipientId readTimestamp:message.timestamp transaction:transaction];
@ -1511,7 +1504,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
return success(); return success();
} }
BOOL shouldSendTranscript = [LKSessionProtocol shouldSendTranscriptForMessage:message in:message.thread]; BOOL shouldSendTranscript = [LKSessionMetaProtocol shouldSendTranscriptForMessage:message in:message.thread];
if (!shouldSendTranscript) { if (!shouldSendTranscript) {
return success(); return success();
} }

@ -174,7 +174,7 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
TSThread *thread = [TSContactThread getOrCreateThreadWithContactId:recipientId]; TSThread *thread = [TSContactThread getOrCreateThreadWithContactId:recipientId];
if (![LKSessionProtocol shouldSendReceiptForThread:thread]) { if (![LKSessionMetaProtocol shouldSendReceiptForThread:thread]) {
continue; continue;
} }

@ -286,7 +286,7 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
self.toLinkedDevicesReadReceiptMap[threadUniqueId] = newReadReceipt; self.toLinkedDevicesReadReceiptMap[threadUniqueId] = newReadReceipt;
} }
if (![LKSessionProtocol shouldSendReadReceiptForThread:message.thread]) { if (![LKSessionMetaProtocol shouldSendReceiptForThread:message.thread]) {
return; return;
} }

@ -323,7 +323,7 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
return return
} }
if !SessionProtocol.shouldSendTypingIndicator(for: thread) { return } if !SessionMetaProtocol.shouldSendTypingIndicator(for: thread) { return }
let message = TypingIndicatorMessage(thread: thread, action: action) let message = TypingIndicatorMessage(thread: thread, action: action)
messageSender.sendPromise(message: message).retainUntilComplete() messageSender.sendPromise(message: message).retainUntilComplete()

Loading…
Cancel
Save