Merge branch 'nt/uuid-3'

This commit is contained in:
Nora Trapp 2019-07-02 18:35:31 -07:00
commit e499cc75b7
26 changed files with 567 additions and 190 deletions

2
Pods

@ -1 +1 @@
Subproject commit f5635a9cc5cb9aaec40cc2e3a3760148f6b7597d
Subproject commit 8c231c2782d70885e11d37fbe5b8f1176b19b606

View File

@ -906,9 +906,12 @@ typedef enum : NSUInteger {
{
NSMutableArray<NSString *> *result = [NSMutableArray new];
for (SignalServiceAddress *address in self.thread.recipientAddresses) {
if ([[OWSIdentityManager sharedManager] verificationStateForRecipientId:address.transitional_phoneNumber]
== OWSVerificationStateNoLongerVerified) {
[result addObject:address.transitional_phoneNumber];
// TODO UUID
if (address.phoneNumber) {
if ([[OWSIdentityManager sharedManager] verificationStateForRecipientId:address.phoneNumber]
== OWSVerificationStateNoLongerVerified) {
[result addObject:address.transitional_phoneNumber];
}
}
}
return [result copy];
@ -1592,10 +1595,13 @@ typedef enum : NSUInteger {
BOOL isVerified = YES;
for (SignalServiceAddress *address in self.thread.recipientAddresses) {
if ([[OWSIdentityManager sharedManager] verificationStateForRecipientId:address.transitional_phoneNumber]
!= OWSVerificationStateVerified) {
isVerified = NO;
break;
// TODO UUID
if (address.phoneNumber) {
if ([[OWSIdentityManager sharedManager] verificationStateForRecipientId:address.phoneNumber]
!= OWSVerificationStateVerified) {
isVerified = NO;
break;
}
}
}
if (isVerified) {
@ -2289,8 +2295,7 @@ typedef enum : NSUInteger {
}
TSContactThread *contactThread = (TSContactThread *)self.thread;
NSString *displayName =
[self.contactsManager displayNameForAddress:interaction.recipientId.transitional_signalServiceAddress];
NSString *displayName = [self.contactsManager displayNameForAddress:contactThread.contactAddress];
NSString *title =
[NSString stringWithFormat:NSLocalizedString(@"BLOCK_OFFER_ACTIONSHEET_TITLE_FORMAT",
@"Title format for action sheet that offers to block an unknown user."
@ -2309,8 +2314,7 @@ typedef enum : NSUInteger {
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action) {
OWSLogInfo(@"Blocking an unknown user.");
[self.blockingManager
addBlockedAddress:interaction.recipientId.transitional_signalServiceAddress];
[self.blockingManager addBlockedAddress:contactThread.contactAddress];
// Delete the offers.
[self.editingDatabaseConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {

View File

@ -1215,7 +1215,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
// Don't bother adding self to profile whitelist.
shouldHaveAddToProfileWhitelistOffer = NO;
} else {
if ([[self.blockingManager blockedPhoneNumbers] containsObject:recipientAddress.transitional_phoneNumber]) {
if ([self.blockingManager isAddressBlocked:recipientAddress]) {
// Only create "add to contacts" offers for users which are not already blocked.
shouldHaveAddToContactsOffer = NO;
// Only create block offers for users which are not already blocked.
@ -1261,8 +1261,14 @@ static const int kYapDatabaseRangeMaxLength = 25000;
shouldHaveAddToProfileWhitelistOffer = NO;
}
// We can't add a user to contacts that doesn't have a phone number
if (recipientAddress.phoneNumber == nil) {
shouldHaveAddToContactsOffer = NO;
}
BOOL shouldHaveContactOffers
= (shouldHaveBlockOffer || shouldHaveAddToContactsOffer || shouldHaveAddToProfileWhitelistOffer);
if (!shouldHaveContactOffers) {
return nil;
}
@ -1283,7 +1289,6 @@ static const int kYapDatabaseRangeMaxLength = 25000;
hasBlockOffer:shouldHaveBlockOffer
hasAddToContactsOffer:shouldHaveAddToContactsOffer
hasAddToProfileWhitelistOffer:shouldHaveAddToProfileWhitelistOffer
recipientId:recipientAddress.transitional_phoneNumber
beforeInteractionId:firstCallOrMessage.uniqueId];
OWSLogInfo(@"Creating contact offers: %@ (%llu)", offersMessage.uniqueId, offersMessage.sortId);
@ -1564,15 +1569,15 @@ static const int kYapDatabaseRangeMaxLength = 25000;
} else if (interactionType == OWSInteractionType_IncomingMessage) {
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)viewItem.interaction;
NSString *incomingSenderId = incomingMessage.authorAddress.transitional_phoneNumber;
OWSAssertDebug(incomingSenderId.length > 0);
SignalServiceAddress *incomingSenderAddress = incomingMessage.authorAddress;
OWSAssertDebug(incomingSenderAddress.isValid);
BOOL isDisappearingMessage = incomingMessage.hasPerConversationExpiration;
NSString *_Nullable nextIncomingSenderId = nil;
SignalServiceAddress *_Nullable nextIncomingSenderAddress = nil;
if (nextViewItem && nextViewItem.interaction.interactionType == interactionType) {
TSIncomingMessage *nextIncomingMessage = (TSIncomingMessage *)nextViewItem.interaction;
nextIncomingSenderId = nextIncomingMessage.authorAddress.transitional_phoneNumber;
OWSAssertDebug(nextIncomingSenderId.length > 0);
nextIncomingSenderAddress = nextIncomingMessage.authorAddress;
OWSAssertDebug(nextIncomingSenderAddress.isValid);
}
if (nextViewItem && nextViewItem.interaction.interactionType == interactionType) {
@ -1580,8 +1585,9 @@ static const int kYapDatabaseRangeMaxLength = 25000;
// We can skip the "incoming message status" footer in a cluster if the next message
// has the same footer and no "date break" separates us.
// ...but always show the "disappearing messages" animation.
shouldHideFooter = ([timestampText isEqualToString:nextTimestampText] && !nextViewItem.hasCellHeader &&
[NSObject isNullableObject:nextIncomingSenderId equalTo:incomingSenderId]
shouldHideFooter = ([timestampText isEqualToString:nextTimestampText] && !nextViewItem.hasCellHeader
&& ((!incomingSenderAddress && !nextIncomingSenderAddress) ||
[incomingSenderAddress matchesAddress:nextIncomingSenderAddress])
&& !isDisappearingMessage);
}
@ -1594,8 +1600,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
isFirstInCluster = YES;
} else {
TSIncomingMessage *previousIncomingMessage = (TSIncomingMessage *)previousViewItem.interaction;
isFirstInCluster
= ![incomingSenderId isEqual:previousIncomingMessage.authorAddress.transitional_phoneNumber];
isFirstInCluster = ![incomingSenderAddress matchesAddress:previousIncomingMessage.authorAddress];
}
if (nextViewItem == nil) {
@ -1606,8 +1611,7 @@ static const int kYapDatabaseRangeMaxLength = 25000;
isLastInCluster = YES;
} else {
TSIncomingMessage *nextIncomingMessage = (TSIncomingMessage *)nextViewItem.interaction;
isLastInCluster
= ![incomingSenderId isEqual:nextIncomingMessage.authorAddress.transitional_phoneNumber];
isLastInCluster = ![incomingSenderAddress matchesAddress:nextIncomingMessage.authorAddress];
}
if (viewItem.isGroupThread) {
@ -1618,16 +1622,16 @@ static const int kYapDatabaseRangeMaxLength = 25000;
if (previousViewItem && previousViewItem.interaction.interactionType == interactionType) {
TSIncomingMessage *previousIncomingMessage = (TSIncomingMessage *)previousViewItem.interaction;
NSString *previousIncomingSenderId = previousIncomingMessage.authorAddress.transitional_phoneNumber;
OWSAssertDebug(previousIncomingSenderId.length > 0);
SignalServiceAddress *previousIncomingSenderAddress = previousIncomingMessage.authorAddress;
OWSAssertDebug(previousIncomingSenderAddress.isValid);
shouldShowSenderName
= (![NSObject isNullableObject:previousIncomingSenderId equalTo:incomingSenderId]
|| viewItem.hasCellHeader);
shouldShowSenderName = ((!incomingSenderAddress && !previousIncomingSenderAddress)
|| ![incomingSenderAddress matchesAddress:previousIncomingSenderAddress]
|| viewItem.hasCellHeader);
}
if (shouldShowSenderName) {
senderName = [self.contactsManager
attributedContactOrProfileNameForAddress:incomingSenderId.transitional_signalServiceAddress
attributedContactOrProfileNameForAddress:incomingSenderAddress
primaryAttributes:[OWSMessageBubbleView senderNamePrimaryAttributes]
secondaryAttributes:[OWSMessageBubbleView senderNameSecondaryAttributes]];
}
@ -1637,7 +1641,8 @@ static const int kYapDatabaseRangeMaxLength = 25000;
// no "date break" separates us.
shouldShowSenderAvatar = YES;
if (nextViewItem && nextViewItem.interaction.interactionType == interactionType) {
shouldShowSenderAvatar = (![NSObject isNullableObject:nextIncomingSenderId equalTo:incomingSenderId]
shouldShowSenderAvatar = ((!incomingSenderAddress && !nextIncomingSenderAddress)
|| ![incomingSenderAddress matchesAddress:nextIncomingSenderAddress]
|| nextViewItem.hasCellHeader);
}
}

View File

@ -183,9 +183,10 @@ const CGFloat kIconViewLength = 24;
contactThread = (TSContactThread *)self.thread;
}
if (contactThread && [threadName isEqualToString:contactThread.contactAddress.phoneNumber]) {
threadName = [PhoneNumber
bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:contactThread.contactAddress.phoneNumber];
NSString *_Nullable phoneNumber = contactThread.contactAddress.phoneNumber;
if (phoneNumber && [threadName isEqualToString:phoneNumber]) {
threadName = [PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:phoneNumber];
} else if (threadName.length == 0 && [self isGroupThread]) {
threadName = [MessageStrings newGroupDefaultTitle];
}

View File

@ -74,7 +74,7 @@ public class ConversationAvatarImageView: AvatarImageView {
let contactsManager: OWSContactsManager
// nil if group avatar
let recipientId: String?
let recipientAddress: SignalServiceAddress?
// nil if contact avatar
let groupThreadId: String?
@ -86,20 +86,20 @@ public class ConversationAvatarImageView: AvatarImageView {
switch thread {
case let contactThread as TSContactThread:
self.recipientId = contactThread.contactAddress.transitional_phoneNumber
self.recipientAddress = contactThread.contactAddress
self.groupThreadId = nil
case let groupThread as TSGroupThread:
self.recipientId = nil
self.recipientAddress = nil
self.groupThreadId = groupThread.uniqueId
default:
owsFailDebug("unexpected thread type: \(thread)")
self.recipientId = nil
self.recipientAddress = nil
self.groupThreadId = nil
}
super.init(frame: .zero)
if recipientId != nil {
if recipientAddress != nil {
NotificationCenter.default.addObserver(self, selector: #selector(handleOtherUsersProfileChanged(notification:)), name: NSNotification.Name(rawValue: kNSNotificationName_OtherUsersProfileDidChange), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleSignalAccountsChanged(notification:)), name: NSNotification.Name.OWSContactsManagerSignalAccountsDidChange, object: nil)
@ -130,17 +130,17 @@ public class ConversationAvatarImageView: AvatarImageView {
Logger.debug("")
guard let changedAddress = notification.userInfo?[kNSNotificationKey_ProfileAddress] as? SignalServiceAddress else {
owsFailDebug("recipientId was unexpectedly nil")
owsFailDebug("changedAddress was unexpectedly nil")
return
}
guard let recipientId = self.recipientId else {
guard let recipientAddress = self.recipientAddress else {
// shouldn't call this for group threads
owsFailDebug("contactId was unexpectedly nil")
owsFailDebug("recipientAddress was unexpectedly nil")
return
}
guard recipientId == changedAddress.transitional_phoneNumber else {
guard recipientAddress == changedAddress else {
// not this avatar
return
}

View File

@ -1032,11 +1032,8 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan
}
NSString *_Nullable phoneNumber = [self phoneNumberForAddress:address];
NSString *_Nullable profileName = [self.profileManager profileNameForAddress:address];
NSString *_Nullable profileName;
if (!SSKFeatureFlags.allowUUIDOnlyContacts) {
profileName = [self.profileManager profileNameForAddress:address];
}
if (profileName.length > 0) {
NSAttributedString *result = [[NSAttributedString alloc] initWithString:phoneNumber ?: address.stringForDisplay
attributes:primaryAttributes];

View File

@ -317,8 +317,8 @@ message SyncMessage {
}
message Read {
// @required
optional string sender = 1;
optional string senderE164 = 1;
optional string senderUuid = 3;
// @required
optional uint64 timestamp = 2;
}

View File

@ -26,17 +26,21 @@ public struct LinkedDeviceReadReceiptRecord: SDSRecord {
public let uniqueId: String
// Base class properties
public let linkedDeviceReadReceiptSchemaVersion: UInt
public let messageIdTimestamp: UInt64
public let readTimestamp: UInt64
public let senderId: String
public let senderPhoneNumber: String?
public let senderUUID: String?
public enum CodingKeys: String, CodingKey, ColumnExpression, CaseIterable {
case id
case recordType
case uniqueId
case linkedDeviceReadReceiptSchemaVersion
case messageIdTimestamp
case readTimestamp
case senderId
case senderPhoneNumber
case senderUUID
}
public static func columnName(_ column: LinkedDeviceReadReceiptRecord.CodingKeys, fullyQualified: Bool = false) -> String {
@ -72,14 +76,18 @@ extension OWSLinkedDeviceReadReceipt {
case .linkedDeviceReadReceipt:
let uniqueId: String = record.uniqueId
let linkedDeviceReadReceiptSchemaVersion: UInt = record.linkedDeviceReadReceiptSchemaVersion
let messageIdTimestamp: UInt64 = record.messageIdTimestamp
let readTimestamp: UInt64 = record.readTimestamp
let senderId: String = record.senderId
let senderPhoneNumber: String? = record.senderPhoneNumber
let senderUUID: String? = record.senderUUID
return OWSLinkedDeviceReadReceipt(uniqueId: uniqueId,
linkedDeviceReadReceiptSchemaVersion: linkedDeviceReadReceiptSchemaVersion,
messageIdTimestamp: messageIdTimestamp,
readTimestamp: readTimestamp,
senderId: senderId)
senderPhoneNumber: senderPhoneNumber,
senderUUID: senderUUID)
default:
owsFailDebug("Unexpected record type: \(record.recordType)")
@ -116,9 +124,11 @@ extension OWSLinkedDeviceReadReceiptSerializer {
static let idColumn = SDSColumnMetadata(columnName: "id", columnType: .primaryKey, columnIndex: 1)
static let uniqueIdColumn = SDSColumnMetadata(columnName: "uniqueId", columnType: .unicodeString, columnIndex: 2)
// Base class properties
static let messageIdTimestampColumn = SDSColumnMetadata(columnName: "messageIdTimestamp", columnType: .int64, columnIndex: 3)
static let readTimestampColumn = SDSColumnMetadata(columnName: "readTimestamp", columnType: .int64, columnIndex: 4)
static let senderIdColumn = SDSColumnMetadata(columnName: "senderId", columnType: .unicodeString, columnIndex: 5)
static let linkedDeviceReadReceiptSchemaVersionColumn = SDSColumnMetadata(columnName: "linkedDeviceReadReceiptSchemaVersion", columnType: .int64, columnIndex: 3)
static let messageIdTimestampColumn = SDSColumnMetadata(columnName: "messageIdTimestamp", columnType: .int64, columnIndex: 4)
static let readTimestampColumn = SDSColumnMetadata(columnName: "readTimestamp", columnType: .int64, columnIndex: 5)
static let senderPhoneNumberColumn = SDSColumnMetadata(columnName: "senderPhoneNumber", columnType: .unicodeString, isOptional: true, columnIndex: 6)
static let senderUUIDColumn = SDSColumnMetadata(columnName: "senderUUID", columnType: .unicodeString, isOptional: true, columnIndex: 7)
// TODO: We should decide on a naming convention for
// tables that store models.
@ -126,9 +136,11 @@ extension OWSLinkedDeviceReadReceiptSerializer {
recordTypeColumn,
idColumn,
uniqueIdColumn,
linkedDeviceReadReceiptSchemaVersionColumn,
messageIdTimestampColumn,
readTimestampColumn,
senderIdColumn
senderPhoneNumberColumn,
senderUUIDColumn
])
}
@ -454,10 +466,12 @@ class OWSLinkedDeviceReadReceiptSerializer: SDSSerializer {
}
// Base class properties
let linkedDeviceReadReceiptSchemaVersion: UInt = model.linkedDeviceReadReceiptSchemaVersion
let messageIdTimestamp: UInt64 = model.messageIdTimestamp
let readTimestamp: UInt64 = model.readTimestamp
let senderId: String = model.senderId
let senderPhoneNumber: String? = model.senderPhoneNumber
let senderUUID: String? = model.senderUUID
return LinkedDeviceReadReceiptRecord(id: id, recordType: recordType, uniqueId: uniqueId, messageIdTimestamp: messageIdTimestamp, readTimestamp: readTimestamp, senderId: senderId)
return LinkedDeviceReadReceiptRecord(id: id, recordType: recordType, uniqueId: uniqueId, linkedDeviceReadReceiptSchemaVersion: linkedDeviceReadReceiptSchemaVersion, messageIdTimestamp: messageIdTimestamp, readTimestamp: readTimestamp, senderPhoneNumber: senderPhoneNumber, senderUUID: senderUUID)
}
}

View File

@ -7,16 +7,17 @@
NS_ASSUME_NONNULL_BEGIN
@class SDSAnyReadTransaction;
@class SignalServiceAddress;
@interface OWSLinkedDeviceReadReceipt : BaseModel
@property (nonatomic, readonly) NSString *senderId;
@property (nonatomic, readonly) SignalServiceAddress *senderAddress;
@property (nonatomic, readonly) uint64_t messageIdTimestamp;
@property (nonatomic, readonly) uint64_t readTimestamp;
- (instancetype)initWithSenderId:(NSString *)senderId
messageIdTimestamp:(uint64_t)messageIdtimestamp
readTimestamp:(uint64_t)readTimestamp;
- (instancetype)initWithSenderAddress:(SignalServiceAddress *)address
messageIdTimestamp:(uint64_t)messageIdtimestamp
readTimestamp:(uint64_t)readTimestamp;
// --- CODE GENERATION MARKER
@ -26,18 +27,20 @@ NS_ASSUME_NONNULL_BEGIN
// clang-format off
- (instancetype)initWithUniqueId:(NSString *)uniqueId
linkedDeviceReadReceiptSchemaVersion:(NSUInteger)linkedDeviceReadReceiptSchemaVersion
messageIdTimestamp:(uint64_t)messageIdTimestamp
readTimestamp:(uint64_t)readTimestamp
senderId:(NSString *)senderId
NS_SWIFT_NAME(init(uniqueId:messageIdTimestamp:readTimestamp:senderId:));
senderPhoneNumber:(nullable NSString *)senderPhoneNumber
senderUUID:(nullable NSString *)senderUUID
NS_SWIFT_NAME(init(uniqueId:linkedDeviceReadReceiptSchemaVersion:messageIdTimestamp:readTimestamp:senderPhoneNumber:senderUUID:));
// clang-format on
// --- CODE GENERATION MARKER
+ (nullable OWSLinkedDeviceReadReceipt *)findLinkedDeviceReadReceiptWithSenderId:(NSString *)senderId
messageIdTimestamp:(uint64_t)messageIdTimestamp
transaction:(SDSAnyReadTransaction *)transaction;
+ (nullable OWSLinkedDeviceReadReceipt *)findLinkedDeviceReadReceiptWithAddress:(SignalServiceAddress *)address
messageIdTimestamp:(uint64_t)messageIdTimestamp
transaction:(SDSAnyReadTransaction *)transaction;
@end

View File

@ -7,24 +7,34 @@
NS_ASSUME_NONNULL_BEGIN
NSUInteger const OWSLinkedDeviceReadReceiptSchemaVersion = 1;
@interface OWSLinkedDeviceReadReceipt ()
@property (nonatomic, nullable, readonly) NSString *senderPhoneNumber;
@property (nonatomic, nullable, readonly) NSString *senderUUID;
@property (nonatomic, readonly) NSUInteger linkedDeviceReadReceiptSchemaVersion;
@end
@implementation OWSLinkedDeviceReadReceipt
- (instancetype)initWithSenderId:(NSString *)senderId
messageIdTimestamp:(uint64_t)messageIdTimestamp
readTimestamp:(uint64_t)readTimestamp
- (instancetype)initWithSenderAddress:(SignalServiceAddress *)address
messageIdTimestamp:(uint64_t)messageIdTimestamp
readTimestamp:(uint64_t)readTimestamp
{
OWSAssertDebug(senderId.length > 0 && messageIdTimestamp > 0);
OWSAssertDebug(address.isValid && messageIdTimestamp > 0);
NSString *receiptId =
[OWSLinkedDeviceReadReceipt uniqueIdForSenderId:senderId messageIdTimestamp:messageIdTimestamp];
self = [super initWithUniqueId:receiptId];
self = [super init];
if (!self) {
return self;
}
_senderId = senderId;
_senderPhoneNumber = address.phoneNumber;
_senderUUID = address.uuidString;
_messageIdTimestamp = messageIdTimestamp;
_readTimestamp = readTimestamp;
_linkedDeviceReadReceiptSchemaVersion = OWSLinkedDeviceReadReceiptSchemaVersion;
return self;
}
@ -51,6 +61,13 @@ NS_ASSUME_NONNULL_BEGIN
_readTimestamp = _messageIdTimestamp;
}
if (_linkedDeviceReadReceiptSchemaVersion < 1) {
_senderPhoneNumber = [coder decodeObjectForKey:@"senderId"];
OWSAssertDebug(_senderPhoneNumber);
}
_linkedDeviceReadReceiptSchemaVersion = OWSLinkedDeviceReadReceiptSchemaVersion;
return self;
}
@ -62,9 +79,11 @@ NS_ASSUME_NONNULL_BEGIN
// clang-format off
- (instancetype)initWithUniqueId:(NSString *)uniqueId
linkedDeviceReadReceiptSchemaVersion:(NSUInteger)linkedDeviceReadReceiptSchemaVersion
messageIdTimestamp:(uint64_t)messageIdTimestamp
readTimestamp:(uint64_t)readTimestamp
senderId:(NSString *)senderId
senderPhoneNumber:(nullable NSString *)senderPhoneNumber
senderUUID:(nullable NSString *)senderUUID
{
self = [super initWithUniqueId:uniqueId];
@ -72,9 +91,11 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
_linkedDeviceReadReceiptSchemaVersion = linkedDeviceReadReceiptSchemaVersion;
_messageIdTimestamp = messageIdTimestamp;
_readTimestamp = readTimestamp;
_senderId = senderId;
_senderPhoneNumber = senderPhoneNumber;
_senderUUID = senderUUID;
return self;
}
@ -83,21 +104,26 @@ NS_ASSUME_NONNULL_BEGIN
// --- CODE GENERATION MARKER
+ (NSString *)uniqueIdForSenderId:(NSString *)senderId messageIdTimestamp:(uint64_t)messageIdTimestamp
- (SignalServiceAddress *)senderAddress
{
OWSAssertDebug(senderId.length > 0 && messageIdTimestamp > 0);
return [NSString stringWithFormat:@"%@-%llu", senderId, messageIdTimestamp];
return [[SignalServiceAddress alloc] initWithUuidString:self.senderUUID phoneNumber:self.senderPhoneNumber];
}
+ (nullable OWSLinkedDeviceReadReceipt *)findLinkedDeviceReadReceiptWithSenderId:(NSString *)senderId
messageIdTimestamp:(uint64_t)messageIdTimestamp
transaction:(SDSAnyReadTransaction *)transaction
+ (AnyLinkedDeviceReadReceiptFinder *)finder
{
return [AnyLinkedDeviceReadReceiptFinder new];
}
+ (nullable OWSLinkedDeviceReadReceipt *)findLinkedDeviceReadReceiptWithAddress:(SignalServiceAddress *)address
messageIdTimestamp:(uint64_t)messageIdTimestamp
transaction:(SDSAnyReadTransaction *)transaction
{
OWSAssertDebug(address.isValid);
OWSAssertDebug(transaction);
NSString *receiptId =
[OWSLinkedDeviceReadReceipt uniqueIdForSenderId:senderId messageIdTimestamp:messageIdTimestamp];
return [OWSLinkedDeviceReadReceipt anyFetchWithUniqueId:receiptId transaction:transaction];
return [self.finder linkedDeviceReadReceiptForAddress:address
messageIdTimestamp:messageIdTimestamp
transaction:transaction];
}
@end

View File

@ -0,0 +1,168 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import Foundation
@objc
class AnyLinkedDeviceReadReceiptFinder: NSObject {
let grdbAdapter = GRDBLinkedDeviceReadReceiptFinder()
let yapdbAdapter = YAPDBLinkedDeviceReadReceiptFinder()
}
extension AnyLinkedDeviceReadReceiptFinder {
@objc(linkedDeviceReadReceiptForAddress:messageIdTimestamp:transaction:)
func linkedDeviceReadReceipt(for address: SignalServiceAddress, andMessageIdTimestamp timestamp: UInt64, transaction: SDSAnyReadTransaction) -> OWSLinkedDeviceReadReceipt? {
switch transaction.readTransaction {
case .grdbRead(let transaction):
return grdbAdapter.linkedDeviceReadReceipt(for: address, andMessageIdTimestamp: timestamp, transaction: transaction)
case .yapRead(let transaction):
return yapdbAdapter.linkedDeviceReadReceipt(for: address, andMessageIdTimestamp: timestamp, transaction: transaction)
}
}
}
@objc
class GRDBLinkedDeviceReadReceiptFinder: NSObject {
func linkedDeviceReadReceipt(for address: SignalServiceAddress, andMessageIdTimestamp timestamp: UInt64, transaction: GRDBReadTransaction) -> OWSLinkedDeviceReadReceipt? {
if let thread = linkedDeviceReadReceiptForUUID(address.uuid, andMessageIdTimestamp: timestamp, transaction: transaction) {
return thread
} else if let thread = linkedDeviceReadReceiptForPhoneNumber(address.phoneNumber, andMessageIdTimestamp: timestamp, transaction: transaction) {
return thread
} else {
return nil
}
}
private func linkedDeviceReadReceiptForUUID(_ uuid: UUID?, andMessageIdTimestamp timestamp: UInt64, transaction: GRDBReadTransaction) -> OWSLinkedDeviceReadReceipt? {
guard let uuidString = uuid?.uuidString else { return nil }
let sql = "SELECT * FROM \(LinkedDeviceReadReceiptRecord.databaseTableName) WHERE \(linkedDeviceReadReceiptColumn: .senderUUID) = ? AND \(linkedDeviceReadReceiptColumn: .messageIdTimestamp) = ?"
return OWSLinkedDeviceReadReceipt.grdbFetchOne(sql: sql, arguments: [uuidString, timestamp], transaction: transaction)
}
private func linkedDeviceReadReceiptForPhoneNumber(_ phoneNumber: String?, andMessageIdTimestamp timestamp: UInt64, transaction: GRDBReadTransaction) -> OWSLinkedDeviceReadReceipt? {
guard let phoneNumber = phoneNumber else { return nil }
let sql = "SELECT * FROM \(LinkedDeviceReadReceiptRecord.databaseTableName) WHERE \(linkedDeviceReadReceiptColumn: .senderPhoneNumber) = ? AND \(linkedDeviceReadReceiptColumn: .messageIdTimestamp) = ?"
return OWSLinkedDeviceReadReceipt.grdbFetchOne(sql: sql, arguments: [phoneNumber, timestamp], transaction: transaction)
}
}
@objc
class YAPDBLinkedDeviceReadReceiptFinder: NSObject {
private static let uuidKey = "uuidKey"
private static let phoneNumberKey = "phoneNumberKey"
private static let timestampKey = "timestampKey"
private static let phoneNumberIndexName = "index_linkedDeviceReadReceipt_on_senderPhoneNumberAndTimestamp"
private static let uuidIndexName = "index_linkedDeviceReadReceipt_on_senderUUIDAndTimestamp"
func linkedDeviceReadReceipt(for address: SignalServiceAddress, andMessageIdTimestamp timestamp: UInt64, transaction: YapDatabaseReadTransaction) -> OWSLinkedDeviceReadReceipt? {
if let result = linkedDeviceReadReceiptForUUID(address.uuid, andMessageIdTimestamp: timestamp, transaction: transaction) {
return result
} else if let result = linkedDeviceReadReceiptForPhoneNumber(address.phoneNumber, andMessageIdTimestamp: timestamp, transaction: transaction) {
return result
} else {
return nil
}
}
private func linkedDeviceReadReceiptForUUID(_ uuid: UUID?, andMessageIdTimestamp timestamp: UInt64, transaction: YapDatabaseReadTransaction) -> OWSLinkedDeviceReadReceipt? {
guard let uuidString = uuid?.uuidString else { return nil }
guard let ext = transaction.ext(YAPDBLinkedDeviceReadReceiptFinder.uuidIndexName) as? YapDatabaseSecondaryIndexTransaction else {
owsFailDebug("Unexpected transaction type for extension")
return nil
}
let queryFormat = String(
format: "WHERE %@ = \"%@\" AND %@ = \"%lld\"",
YAPDBLinkedDeviceReadReceiptFinder.uuidKey,
uuidString,
YAPDBLinkedDeviceReadReceiptFinder.timestampKey,
timestamp
)
let query = YapDatabaseQuery(string: queryFormat, parameters: [])
var matchedRecord: OWSLinkedDeviceReadReceipt?
ext.enumerateKeysAndObjects(matching: query) { _, _, object, stop in
guard let record = object as? OWSLinkedDeviceReadReceipt else {
return
}
matchedRecord = record
stop.pointee = true
}
return matchedRecord
}
private func linkedDeviceReadReceiptForPhoneNumber(_ phoneNumber: String?, andMessageIdTimestamp timestamp: UInt64, transaction: YapDatabaseReadTransaction) -> OWSLinkedDeviceReadReceipt? {
guard let phoneNumber = phoneNumber else { return nil }
guard let ext = transaction.ext(YAPDBLinkedDeviceReadReceiptFinder.phoneNumberIndexName) as? YapDatabaseSecondaryIndexTransaction else {
owsFailDebug("Unexpected transaction type for extension")
return nil
}
let queryFormat = String(
format: "WHERE %@ = \"%@\" AND %@ = \"%lld\"",
YAPDBLinkedDeviceReadReceiptFinder.phoneNumberKey,
phoneNumber,
YAPDBLinkedDeviceReadReceiptFinder.timestampKey,
timestamp
)
let query = YapDatabaseQuery(string: queryFormat, parameters: [])
var matchedRecord: OWSLinkedDeviceReadReceipt?
ext.enumerateKeysAndObjects(matching: query) { _, _, object, stop in
guard let record = object as? OWSLinkedDeviceReadReceipt else {
return
}
matchedRecord = record
stop.pointee = true
}
return matchedRecord
}
@objc
static func asyncRegisterDatabaseExtensions(_ storage: OWSStorage) {
storage.asyncRegister(indexUUIDExtension(), withName: uuidIndexName)
storage.asyncRegister(indexPhoneNumberExtension(), withName: phoneNumberIndexName)
}
private static func indexUUIDExtension() -> YapDatabaseSecondaryIndex {
let setup = YapDatabaseSecondaryIndexSetup()
setup.addColumn(uuidKey, with: .text)
setup.addColumn(timestampKey, with: .integer)
let handler = YapDatabaseSecondaryIndexHandler.withObjectBlock { _, dict, _, _, object in
guard let indexableObject = object as? OWSLinkedDeviceReadReceipt else {
return
}
dict[uuidKey] = indexableObject.senderUUID
dict[timestampKey] = indexableObject.messageIdTimestamp
}
return YapDatabaseSecondaryIndex(setup: setup, handler: handler, versionTag: "1")
}
private static func indexPhoneNumberExtension() -> YapDatabaseSecondaryIndex {
let setup = YapDatabaseSecondaryIndexSetup()
setup.addColumn(phoneNumberKey, with: .text)
setup.addColumn(timestampKey, with: .integer)
let handler = YapDatabaseSecondaryIndexHandler.withObjectBlock { _, dict, _, _, object in
guard let indexableObject = object as? OWSLinkedDeviceReadReceipt else {
return
}
dict[phoneNumberKey] = indexableObject.senderPhoneNumber
dict[timestampKey] = indexableObject.messageIdTimestamp
}
return YapDatabaseSecondaryIndex(setup: setup, handler: handler, versionTag: "1")
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "OWSReadReceiptsForLinkedDevicesMessage.h"
@ -38,7 +38,10 @@ NS_ASSUME_NONNULL_BEGIN
SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder];
for (OWSLinkedDeviceReadReceipt *readReceipt in self.readReceipts) {
SSKProtoSyncMessageReadBuilder *readProtoBuilder =
[SSKProtoSyncMessageRead builderWithSender:readReceipt.senderId timestamp:readReceipt.messageIdTimestamp];
[SSKProtoSyncMessageRead builderWithTimestamp:readReceipt.messageIdTimestamp];
[readProtoBuilder setSenderE164:readReceipt.senderAddress.phoneNumber];
[readProtoBuilder setSenderUuid:readReceipt.senderAddress.uuidString];
NSError *error;
SSKProtoSyncMessageRead *_Nullable readProto = [readProtoBuilder buildAndReturnError:&error];

View File

@ -84,7 +84,7 @@ class OWSContactOffersInteractionSerializer: SDSSerializer {
let protocolVersion: UInt? = nil
let quotedMessage: Data? = nil
let read: Bool? = nil
let recipientId: String? = model.recipientId
let recipientId: String? = nil
let recipientStateMap: Data? = nil
let schemaVersion: UInt? = nil
let senderId: String? = nil

View File

@ -14,9 +14,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) BOOL hasAddToContactsOffer;
@property (nonatomic, readonly) BOOL hasAddToProfileWhitelistOffer;
// TODO - remove this recipientId param
// it's redundant with the interaction's TSContactThread
@property (nonatomic, readonly) NSString *recipientId;
@property (nonatomic, readonly) NSString *beforeInteractionId;
- (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread NS_UNAVAILABLE;
@ -30,7 +27,6 @@ NS_ASSUME_NONNULL_BEGIN
hasBlockOffer:(BOOL)hasBlockOffer
hasAddToContactsOffer:(BOOL)hasAddToContactsOffer
hasAddToProfileWhitelistOffer:(BOOL)hasAddToProfileWhitelistOffer
recipientId:(NSString *)recipientId
beforeInteractionId:(NSString *)beforeInteractionId NS_DESIGNATED_INITIALIZER;
// --- CODE GENERATION MARKER
@ -48,8 +44,7 @@ NS_ASSUME_NONNULL_BEGIN
hasAddToContactsOffer:(BOOL)hasAddToContactsOffer
hasAddToProfileWhitelistOffer:(BOOL)hasAddToProfileWhitelistOffer
hasBlockOffer:(BOOL)hasBlockOffer
recipientId:(NSString *)recipientId
NS_SWIFT_NAME(init(uniqueId:receivedAtTimestamp:sortId:timestamp:uniqueThreadId:beforeInteractionId:hasAddToContactsOffer:hasAddToProfileWhitelistOffer:hasBlockOffer:recipientId:));
NS_SWIFT_NAME(init(uniqueId:receivedAtTimestamp:sortId:timestamp:uniqueThreadId:beforeInteractionId:hasAddToContactsOffer:hasAddToProfileWhitelistOffer:hasBlockOffer:));
// clang-format on

View File

@ -27,7 +27,6 @@ NS_ASSUME_NONNULL_BEGIN
hasBlockOffer:(BOOL)hasBlockOffer
hasAddToContactsOffer:(BOOL)hasAddToContactsOffer
hasAddToProfileWhitelistOffer:(BOOL)hasAddToProfileWhitelistOffer
recipientId:(NSString *)recipientId
beforeInteractionId:(NSString *)beforeInteractionId
{
self = [super initWithUniqueId:uniqueId timestamp:timestamp inThread:thread];
@ -39,8 +38,6 @@ NS_ASSUME_NONNULL_BEGIN
_hasBlockOffer = hasBlockOffer;
_hasAddToContactsOffer = hasAddToContactsOffer;
_hasAddToProfileWhitelistOffer = hasAddToProfileWhitelistOffer;
OWSAssertDebug(recipientId.length > 0);
_recipientId = recipientId;
_beforeInteractionId = beforeInteractionId;
return self;
@ -61,7 +58,6 @@ NS_ASSUME_NONNULL_BEGIN
hasAddToContactsOffer:(BOOL)hasAddToContactsOffer
hasAddToProfileWhitelistOffer:(BOOL)hasAddToProfileWhitelistOffer
hasBlockOffer:(BOOL)hasBlockOffer
recipientId:(NSString *)recipientId
{
self = [super initWithUniqueId:uniqueId
receivedAtTimestamp:receivedAtTimestamp
@ -77,7 +73,6 @@ NS_ASSUME_NONNULL_BEGIN
_hasAddToContactsOffer = hasAddToContactsOffer;
_hasAddToProfileWhitelistOffer = hasAddToProfileWhitelistOffer;
_hasBlockOffer = hasBlockOffer;
_recipientId = recipientId;
return self;
}

View File

@ -312,7 +312,6 @@ extension TSInteraction {
let hasAddToContactsOffer: Bool = try SDSDeserialization.required(record.hasAddToContactsOffer, name: "hasAddToContactsOffer")
let hasAddToProfileWhitelistOffer: Bool = try SDSDeserialization.required(record.hasAddToProfileWhitelistOffer, name: "hasAddToProfileWhitelistOffer")
let hasBlockOffer: Bool = try SDSDeserialization.required(record.hasBlockOffer, name: "hasBlockOffer")
let recipientId: String = try SDSDeserialization.required(record.recipientId, name: "recipientId")
return OWSContactOffersInteraction(uniqueId: uniqueId,
receivedAtTimestamp: receivedAtTimestamp,
@ -322,8 +321,7 @@ extension TSInteraction {
beforeInteractionId: beforeInteractionId,
hasAddToContactsOffer: hasAddToContactsOffer,
hasAddToProfileWhitelistOffer: hasAddToProfileWhitelistOffer,
hasBlockOffer: hasBlockOffer,
recipientId: recipientId)
hasBlockOffer: hasBlockOffer)
case .disappearingConfigurationUpdateInfoMessage:

View File

@ -574,7 +574,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
return nil;
}
[recipientIds addObject:recipientAddress.transitional_phoneNumber];
// TODO UUID
if (recipientAddress.phoneNumber) {
[recipientIds addObject:recipientAddress.phoneNumber];
}
if ([recipientIds containsObject:self.tsAccountManager.localNumber]) {
OWSFailDebug(@"Message send recipients should not include self.");

View File

@ -1,11 +1,12 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@class OWSPrimaryStorage;
@class SSKProtoEnvelope;
@class SignalServiceAddress;
@interface OWSOutgoingReceiptManager : NSObject
@ -15,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)enqueueDeliveryReceiptForEnvelope:(SSKProtoEnvelope *)envelope;
- (void)enqueueReadReceiptForEnvelope:(NSString *)messageAuthorId timestamp:(uint64_t)timestamp;
- (void)enqueueReadReceiptForAddress:(SignalServiceAddress *)messageAuthorAddress timestamp:(uint64_t)timestamp;
@end

View File

@ -165,15 +165,30 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
NSMutableArray<AnyPromise *> *sendPromises = [NSMutableArray array];
for (NSString *recipientId in queuedReceiptMap) {
NSSet<NSNumber *> *timestamps = queuedReceiptMap[recipientId];
for (NSString *identifier in queuedReceiptMap) {
// The identifier could be either a UUID or a phone number,
// check if it's a valid UUID. If not, assume it's a phone number.
SignalServiceAddress *address;
NSUUID *_Nullable uuid = [[NSUUID alloc] initWithUUIDString:identifier];
if (uuid) {
address = [[SignalServiceAddress alloc] initWithUuid:uuid phoneNumber:nil];
} else {
address = [[SignalServiceAddress alloc] initWithPhoneNumber:identifier];
}
if (!address.isValid) {
OWSFailDebug(@"Unexpected identifier.");
continue;
}
NSSet<NSNumber *> *timestamps = queuedReceiptMap[identifier];
if (timestamps.count < 1) {
OWSFailDebug(@"Missing timestamps.");
continue;
}
TSThread *thread =
[TSContactThread getOrCreateThreadWithContactAddress:recipientId.transitional_signalServiceAddress];
TSThread *thread = [TSContactThread getOrCreateThreadWithContactAddress:address];
OWSReceiptsForSenderMessage *message;
NSString *receiptName;
switch (receiptType) {
@ -198,7 +213,7 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
// DURABLE CLEANUP - we could replace the custom durability logic in this class
// with a durable JobQueue.
[self dequeueReceiptsWithRecipientId:recipientId timestamps:timestamps receiptType:receiptType];
[self dequeueReceiptsForAddress:address timestamps:timestamps receiptType:receiptType];
// The value doesn't matter, we just need any non-NSError value.
resolve(@(1));
@ -208,7 +223,7 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
if (error.domain == OWSSignalServiceKitErrorDomain
&& error.code == OWSErrorCodeNoSuchSignalRecipient) {
[self dequeueReceiptsWithRecipientId:recipientId timestamps:timestamps receiptType:receiptType];
[self dequeueReceiptsForAddress:address timestamps:timestamps receiptType:receiptType];
}
resolve(error);
@ -222,66 +237,113 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
- (void)enqueueDeliveryReceiptForEnvelope:(SSKProtoEnvelope *)envelope
{
[self enqueueReceiptWithRecipientId:envelope.sourceE164
timestamp:envelope.timestamp
receiptType:OWSReceiptType_Delivery];
[self enqueueReadReceiptForAddress:envelope.sourceAddress
timestamp:envelope.timestamp
receiptType:OWSReceiptType_Delivery];
}
- (void)enqueueReadReceiptForEnvelope:(NSString *)messageAuthorId timestamp:(uint64_t)timestamp {
[self enqueueReceiptWithRecipientId:messageAuthorId timestamp:timestamp receiptType:OWSReceiptType_Read];
- (void)enqueueReadReceiptForAddress:(SignalServiceAddress *)address timestamp:(uint64_t)timestamp
{
[self enqueueReadReceiptForAddress:address timestamp:timestamp receiptType:OWSReceiptType_Read];
}
- (void)enqueueReceiptWithRecipientId:(NSString *)recipientId
timestamp:(uint64_t)timestamp
receiptType:(OWSReceiptType)receiptType {
- (void)enqueueReadReceiptForAddress:(SignalServiceAddress *)address
timestamp:(uint64_t)timestamp
receiptType:(OWSReceiptType)receiptType
{
NSString *collection = [self collectionForReceiptType:receiptType];
if (recipientId.length < 1) {
OWSFailDebug(@"Invalid recipient id.");
return;
}
OWSAssertDebug(address.isValid);
if (timestamp < 1) {
OWSFailDebug(@"Invalid timestamp.");
return;
}
dispatch_async(self.serialQueue, ^{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSSet<NSNumber *> *_Nullable oldTimestamps = [transaction objectForKey:recipientId inCollection:collection];
NSString *identifier = address.uuidString ?: address.phoneNumber;
NSSet<NSNumber *> *_Nullable oldUUIDTimestamps;
if (address.uuidString) {
oldUUIDTimestamps = [transaction objectForKey:address.uuidString inCollection:collection];
}
NSSet<NSNumber *> *_Nullable oldPhoneNumberTimestamps;
if (address.phoneNumber) {
oldPhoneNumberTimestamps = [transaction objectForKey:address.phoneNumber inCollection:collection];
}
NSSet<NSNumber *> *_Nullable oldTimestamps;
// Unexpectedly have entries both on phone number and UUID, defer to UUID
if (oldUUIDTimestamps && oldPhoneNumberTimestamps) {
[transaction removeObjectForKey:address.phoneNumber inCollection:collection];
// If we have timestamps only under phone number, but know the UUID, migrate them lazily
} else if (oldPhoneNumberTimestamps && address.uuidString) {
[transaction removeObjectForKey:address.phoneNumber inCollection:collection];
}
NSMutableSet<NSNumber *> *newTimestamps
= (oldTimestamps ? [oldTimestamps mutableCopy] : [NSMutableSet new]);
[newTimestamps addObject:@(timestamp)];
[transaction setObject:newTimestamps forKey:recipientId inCollection:collection];
[transaction setObject:newTimestamps forKey:identifier inCollection:collection];
}];
[self process];
});
}
- (void)dequeueReceiptsWithRecipientId:(NSString *)recipientId
timestamps:(NSSet<NSNumber *> *)timestamps
receiptType:(OWSReceiptType)receiptType {
- (void)dequeueReceiptsForAddress:(SignalServiceAddress *)address
timestamps:(NSSet<NSNumber *> *)timestamps
receiptType:(OWSReceiptType)receiptType
{
NSString *collection = [self collectionForReceiptType:receiptType];
if (recipientId.length < 1) {
OWSFailDebug(@"Invalid recipient id.");
return;
}
OWSAssertDebug(address.isValid);
if (timestamps.count < 1) {
OWSFailDebug(@"Invalid timestamps.");
return;
}
dispatch_async(self.serialQueue, ^{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSSet<NSNumber *> *_Nullable oldTimestamps = [transaction objectForKey:recipientId inCollection:collection];
NSString *identifier = address.uuidString ?: address.phoneNumber;
NSSet<NSNumber *> *_Nullable oldUUIDTimestamps;
if (address.uuidString) {
oldUUIDTimestamps = [transaction objectForKey:address.uuidString inCollection:collection];
}
NSSet<NSNumber *> *_Nullable oldPhoneNumberTimestamps;
if (address.phoneNumber) {
oldPhoneNumberTimestamps = [transaction objectForKey:address.phoneNumber inCollection:collection];
}
NSSet<NSNumber *> *_Nullable oldTimestamps = oldUUIDTimestamps;
// Unexpectedly have entries both on phone number and UUID, defer to UUID
if (oldUUIDTimestamps && oldPhoneNumberTimestamps) {
[transaction removeObjectForKey:address.phoneNumber inCollection:collection];
// If we have timestamps only under phone number, but know the UUID, migrate them lazily
} else if (oldPhoneNumberTimestamps && address.uuidString) {
oldTimestamps = oldPhoneNumberTimestamps;
[transaction removeObjectForKey:address.phoneNumber inCollection:collection];
// We don't know the UUID, just use the phone number timestamps.
} else if (oldPhoneNumberTimestamps) {
oldTimestamps = oldPhoneNumberTimestamps;
}
NSMutableSet<NSNumber *> *newTimestamps
= (oldTimestamps ? [oldTimestamps mutableCopy] : [NSMutableSet new]);
[newTimestamps minusSet:timestamps];
if (newTimestamps.count > 0) {
[transaction setObject:newTimestamps forKey:recipientId inCollection:collection];
[transaction setObject:newTimestamps forKey:identifier inCollection:collection];
} else {
[transaction removeObjectForKey:recipientId inCollection:collection];
[transaction removeObjectForKey:identifier inCollection:collection];
}
}];
});

View File

@ -303,13 +303,13 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
NSString *threadUniqueId = message.uniqueThreadId;
OWSAssertDebug(threadUniqueId.length > 0);
NSString *messageAuthorId = message.authorAddress.transitional_phoneNumber;
OWSAssertDebug(messageAuthorId.length > 0);
SignalServiceAddress *messageAuthorAddress = message.authorAddress;
OWSAssertDebug(messageAuthorAddress.isValid);
OWSLinkedDeviceReadReceipt *newReadReceipt =
[[OWSLinkedDeviceReadReceipt alloc] initWithSenderId:messageAuthorId
messageIdTimestamp:message.timestamp
readTimestamp:[NSDate ows_millisecondTimeStamp]];
[[OWSLinkedDeviceReadReceipt alloc] initWithSenderAddress:messageAuthorAddress
messageIdTimestamp:message.timestamp
readTimestamp:[NSDate ows_millisecondTimeStamp]];
OWSLinkedDeviceReadReceipt *_Nullable oldReadReceipt = self.toLinkedDevicesReadReceiptMap[threadUniqueId];
if (oldReadReceipt && oldReadReceipt.messageIdTimestamp > newReadReceipt.messageIdTimestamp) {
@ -328,7 +328,8 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
if ([self areReadReceiptsEnabled]) {
OWSLogVerbose(@"Enqueuing read receipt for sender.");
[self.outgoingReceiptManager enqueueReadReceiptForEnvelope:messageAuthorId timestamp:message.timestamp];
[self.outgoingReceiptManager enqueueReadReceiptForAddress:messageAuthorAddress
timestamp:message.timestamp];
}
[self scheduleProcessing];
@ -412,10 +413,6 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
{
OWSAssertDebug(message);
OWSAssertDebug(transaction);
if (SSKFeatureFlags.allowUUIDOnlyContacts) {
// UUID TODO
return;
}
SignalServiceAddress *senderAddress = message.authorAddress;
uint64_t timestamp = message.timestamp;
@ -425,9 +422,9 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
}
OWSLinkedDeviceReadReceipt *_Nullable readReceipt =
[OWSLinkedDeviceReadReceipt findLinkedDeviceReadReceiptWithSenderId:senderAddress.transitional_phoneNumber
messageIdTimestamp:timestamp
transaction:transaction];
[OWSLinkedDeviceReadReceipt findLinkedDeviceReadReceiptWithAddress:senderAddress
messageIdTimestamp:timestamp
transaction:transaction];
if (!readReceipt) {
return;
}
@ -444,13 +441,10 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
OWSAssertDebug(transaction);
for (SSKProtoSyncMessageRead *readReceiptProto in readReceiptProtos) {
NSString *_Nullable senderId = readReceiptProto.sender;
SignalServiceAddress *_Nullable senderAddress = readReceiptProto.senderAddress;
uint64_t messageIdTimestamp = readReceiptProto.timestamp;
if (senderId.length == 0) {
OWSFailDebug(@"senderId was unexpectedly nil");
continue;
}
OWSAssertDebug(senderAddress.isValid);
if (messageIdTimestamp == 0) {
OWSFailDebug(@"messageIdTimestamp was unexpectedly 0");
@ -475,9 +469,9 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
// Received read receipt for unknown incoming message.
// Persist in case we receive the incoming message later.
OWSLinkedDeviceReadReceipt *readReceipt =
[[OWSLinkedDeviceReadReceipt alloc] initWithSenderId:senderId
messageIdTimestamp:messageIdTimestamp
readTimestamp:readTimestamp];
[[OWSLinkedDeviceReadReceipt alloc] initWithSenderAddress:senderAddress
messageIdTimestamp:messageIdTimestamp
readTimestamp:readTimestamp];
[readReceipt anyInsertWithTransaction:transaction];
}
}

View File

@ -4508,13 +4508,19 @@ extension SSKProtoSyncMessageRequest.SSKProtoSyncMessageRequestBuilder {
// MARK: - SSKProtoSyncMessageReadBuilder
@objc public class func builder(sender: String, timestamp: UInt64) -> SSKProtoSyncMessageReadBuilder {
return SSKProtoSyncMessageReadBuilder(sender: sender, timestamp: timestamp)
@objc public class func builder(timestamp: UInt64) -> SSKProtoSyncMessageReadBuilder {
return SSKProtoSyncMessageReadBuilder(timestamp: timestamp)
}
// asBuilder() constructs a builder that reflects the proto's contents.
@objc public func asBuilder() -> SSKProtoSyncMessageReadBuilder {
let builder = SSKProtoSyncMessageReadBuilder(sender: sender, timestamp: timestamp)
let builder = SSKProtoSyncMessageReadBuilder(timestamp: timestamp)
if let _value = senderE164 {
builder.setSenderE164(_value)
}
if let _value = senderUuid {
builder.setSenderUuid(_value)
}
return builder
}
@ -4524,15 +4530,18 @@ extension SSKProtoSyncMessageRequest.SSKProtoSyncMessageRequestBuilder {
@objc fileprivate override init() {}
@objc fileprivate init(sender: String, timestamp: UInt64) {
@objc fileprivate init(timestamp: UInt64) {
super.init()
setSender(sender)
setTimestamp(timestamp)
}
@objc public func setSender(_ valueParam: String) {
proto.sender = valueParam
@objc public func setSenderE164(_ valueParam: String) {
proto.senderE164 = valueParam
}
@objc public func setSenderUuid(_ valueParam: String) {
proto.senderUuid = valueParam
}
@objc public func setTimestamp(_ valueParam: UInt64) {
@ -4550,15 +4559,31 @@ extension SSKProtoSyncMessageRequest.SSKProtoSyncMessageRequestBuilder {
fileprivate let proto: SignalServiceProtos_SyncMessage.Read
@objc public let sender: String
@objc public let timestamp: UInt64
@objc public var senderE164: String? {
guard proto.hasSenderE164 else {
return nil
}
return proto.senderE164
}
@objc public var hasSenderE164: Bool {
return proto.hasSenderE164
}
@objc public var senderUuid: String? {
guard proto.hasSenderUuid else {
return nil
}
return proto.senderUuid
}
@objc public var hasSenderUuid: Bool {
return proto.hasSenderUuid
}
private init(proto: SignalServiceProtos_SyncMessage.Read,
sender: String,
timestamp: UInt64) {
self.proto = proto
self.sender = sender
self.timestamp = timestamp
}
@ -4573,11 +4598,6 @@ extension SSKProtoSyncMessageRequest.SSKProtoSyncMessageRequestBuilder {
}
fileprivate class func parseProto(_ proto: SignalServiceProtos_SyncMessage.Read) throws -> SSKProtoSyncMessageRead {
guard proto.hasSender else {
throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: sender")
}
let sender = proto.sender
guard proto.hasTimestamp else {
throw SSKProtoError.invalidProtobuf(description: "\(logTag) missing required field: timestamp")
}
@ -4588,7 +4608,6 @@ extension SSKProtoSyncMessageRequest.SSKProtoSyncMessageRequestBuilder {
// MARK: - End Validation Logic for SSKProtoSyncMessageRead -
let result = SSKProtoSyncMessageRead(proto: proto,
sender: sender,
timestamp: timestamp)
return result
}

View File

@ -1876,15 +1876,23 @@ struct SignalServiceProtos_SyncMessage {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var sender: String {
get {return _sender ?? String()}
set {_sender = newValue}
var senderE164: String {
get {return _senderE164 ?? String()}
set {_senderE164 = newValue}
}
/// Returns true if `sender` has been explicitly set.
var hasSender: Bool {return self._sender != nil}
/// Clears the value of `sender`. Subsequent reads from it will return its default value.
mutating func clearSender() {self._sender = nil}
/// Returns true if `senderE164` has been explicitly set.
var hasSenderE164: Bool {return self._senderE164 != nil}
/// Clears the value of `senderE164`. Subsequent reads from it will return its default value.
mutating func clearSenderE164() {self._senderE164 = nil}
var senderUuid: String {
get {return _senderUuid ?? String()}
set {_senderUuid = newValue}
}
/// Returns true if `senderUuid` has been explicitly set.
var hasSenderUuid: Bool {return self._senderUuid != nil}
/// Clears the value of `senderUuid`. Subsequent reads from it will return its default value.
mutating func clearSenderUuid() {self._senderUuid = nil}
/// @required
var timestamp: UInt64 {
@ -1900,7 +1908,8 @@ struct SignalServiceProtos_SyncMessage {
init() {}
fileprivate var _sender: String? = nil
fileprivate var _senderE164: String? = nil
fileprivate var _senderUuid: String? = nil
fileprivate var _timestamp: UInt64? = nil
}
@ -4642,32 +4651,38 @@ extension SignalServiceProtos_SyncMessage.Request.TypeEnum: SwiftProtobuf._Proto
extension SignalServiceProtos_SyncMessage.Read: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
static let protoMessageName: String = SignalServiceProtos_SyncMessage.protoMessageName + ".Read"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "sender"),
1: .same(proto: "senderE164"),
3: .same(proto: "senderUuid"),
2: .same(proto: "timestamp"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularStringField(value: &self._sender)
case 1: try decoder.decodeSingularStringField(value: &self._senderE164)
case 2: try decoder.decodeSingularUInt64Field(value: &self._timestamp)
case 3: try decoder.decodeSingularStringField(value: &self._senderUuid)
default: break
}
}
}
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if let v = self._sender {
if let v = self._senderE164 {
try visitor.visitSingularStringField(value: v, fieldNumber: 1)
}
if let v = self._timestamp {
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 2)
}
if let v = self._senderUuid {
try visitor.visitSingularStringField(value: v, fieldNumber: 3)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: SignalServiceProtos_SyncMessage.Read, rhs: SignalServiceProtos_SyncMessage.Read) -> Bool {
if lhs._sender != rhs._sender {return false}
if lhs._senderE164 != rhs._senderE164 {return false}
if lhs._senderUuid != rhs._senderUuid {return false}
if lhs._timestamp != rhs._timestamp {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true

View File

@ -53,3 +53,53 @@ public extension SSKProtoEnvelope {
return address
}
}
@objc
public extension SSKProtoSyncMessageRead {
var hasValidSender: Bool {
return senderAddress != nil
}
var senderAddress: SignalServiceAddress? {
let uuidString: String? = {
guard hasSenderUuid else {
return nil
}
guard let senderUuid = senderUuid else {
owsFailDebug("senderUuid was unexpectedly nil")
return nil
}
return senderUuid
}()
let phoneNumber: String? = {
guard hasSenderE164 else {
// Shouldn't happen in prod yet
assert(FeatureFlags.allowUUIDOnlyContacts)
return nil
}
guard let senderE164 = senderE164 else {
owsFailDebug("senderE164 was unexpectedly nil")
return nil
}
guard senderE164.count > 0 else {
owsFailDebug("senderE164 was unexpectedly empty")
return nil
}
return senderE164
}()
let address = SignalServiceAddress(uuidString: uuidString, phoneNumber: phoneNumber)
guard address.isValid else {
owsFailDebug("address was unexpectedly invalid")
return nil
}
return address
}
}

View File

@ -541,6 +541,19 @@ public class GRDBDatabaseStorageAdapter: NSObject {
on: UserProfileRecord.databaseTableName,
columns: [UserProfileRecord.columnName(.recipientUUID)]
)
// Linked Device Read Receipts
try db.create(
index: "index_linkedDeviceReadReceipt_on_senderPhoneNumberAndTimestamp",
on: LinkedDeviceReadReceiptRecord.databaseTableName,
columns: [LinkedDeviceReadReceiptRecord.columnName(.senderPhoneNumber), LinkedDeviceReadReceiptRecord.columnName(.messageIdTimestamp)]
)
try db.create(
index: "index_linkedDeviceReadReceipt_on_senderUUIDAndTimestamp",
on: LinkedDeviceReadReceiptRecord.databaseTableName,
columns: [LinkedDeviceReadReceiptRecord.columnName(.senderUUID), LinkedDeviceReadReceiptRecord.columnName(.messageIdTimestamp)]
)
}
return migrator
}()

View File

@ -189,4 +189,14 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark -
@interface OWSLinkedDeviceReadReceipt (SDS)
@property (nonatomic, nullable, readonly) NSString *senderPhoneNumber;
@property (nonatomic, nullable, readonly) NSString *senderUUID;
@property (nonatomic, readonly) NSUInteger linkedDeviceReadReceiptSchemaVersion;
@end
#pragma mark -
NS_ASSUME_NONNULL_END

View File

@ -231,6 +231,7 @@ void VerifyRegistrationsForPrimaryStorage(OWSStorage *storage, dispatch_block_t
[TSDatabaseView asyncRegisterLazyRestoreAttachmentsDatabaseView:self];
[YAPDBJobRecordFinderSetup asyncRegisterDatabaseExtensionObjCWithStorage:self];
[YAPDBSignalServiceAddressIndex asyncRegisterDatabaseExtensions:self];
[YAPDBLinkedDeviceReadReceiptFinder asyncRegisterDatabaseExtensions:self];
[self.database
flushExtensionRequestsWithCompletionQueue:dispatch_get_global_queue(