Refactor friend request protocol

pull/175/head
Mikunj 5 years ago
parent 4dcf26b7e7
commit 23188c7e5d

@ -4466,14 +4466,14 @@ typedef enum : NSUInteger {
- (void)acceptFriendRequest:(TSIncomingMessage *)friendRequest - (void)acceptFriendRequest:(TSIncomingMessage *)friendRequest
{ {
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKFriendRequestProtocol acceptFriendRequestFrom:friendRequest.authorId in:self.thread using:transaction]; [LKFriendRequestProtocol acceptFriendRequestInThread:self.thread using:transaction];
}]; }];
} }
- (void)declineFriendRequest:(TSIncomingMessage *)friendRequest - (void)declineFriendRequest:(TSIncomingMessage *)friendRequest
{ {
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKFriendRequestProtocol declineFriendRequest:friendRequest in:self.thread using:transaction]; [LKFriendRequestProtocol declineFriendRequestInThread:self.thread using:transaction];
}]; }];
} }

@ -15,6 +15,11 @@ public final class FriendRequestProtocol : NSObject {
internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() }
// Mark: - Status
private static func isPendingFriendRequest(_ status: LKFriendRequestStatus) -> Bool {
return status == .requestSending || status == .requestSent || status == .requestReceived
}
// MARK: - General // MARK: - General
@objc(shouldInputBarBeEnabledForThread:) @objc(shouldInputBarBeEnabledForThread:)
public static func shouldInputBarBeEnabled(for thread: TSThread) -> Bool { public static func shouldInputBarBeEnabled(for thread: TSThread) -> Bool {
@ -23,14 +28,17 @@ public final class FriendRequestProtocol : NSObject {
// 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 SessionProtocol.isMessageNoteToSelf(thread) { return true }
let contactID = thread.contactIdentifier() let contactID = thread.contactIdentifier()
var linkedDeviceThreads: Set<TSContactThread> = [] var friendRequestStatuses: [LKFriendRequestStatus] = []
storage.dbReadConnection.read { transaction in storage.dbReadConnection.read { transaction in
linkedDeviceThreads = LokiDatabaseUtilities.getLinkedDeviceThreads(for: contactID, in: transaction) let linkedDeviceThreads = LokiDatabaseUtilities.getLinkedDeviceThreads(for: contactID, in: transaction)
friendRequestStatuses = linkedDeviceThreads.map { thread in
storage.getFriendRequestStatus(forContact: thread.contactIdentifier(), transaction: transaction)
}
} }
// If the current user is friends with any of the other user's devices, the input bar should be enabled // If the current user is friends with any of the other user's devices, the input bar should be enabled
if linkedDeviceThreads.contains(where: { $0.isContactFriend }) { return true } if friendRequestStatuses.contains(where: { $0 == .friends }) { return true }
// If no friend request has been sent, the input bar should be enabled // If no friend request has been sent, the input bar should be enabled
if !linkedDeviceThreads.contains(where: { $0.hasPendingFriendRequest }) { return true } if !friendRequestStatuses.contains(where: { isPendingFriendRequest($0) }) { return true }
// There must be a pending friend request // There must be a pending friend request
return false return false
} }
@ -42,30 +50,38 @@ public final class FriendRequestProtocol : NSObject {
// 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 SessionProtocol.isMessageNoteToSelf(thread) { return true }
let contactID = thread.contactIdentifier() let contactID = thread.contactIdentifier()
var linkedDeviceThreads: Set<TSContactThread> = [] var friendRequestStatuses: [LKFriendRequestStatus] = []
storage.dbReadConnection.read { transaction in storage.dbReadConnection.read { transaction in
linkedDeviceThreads = LokiDatabaseUtilities.getLinkedDeviceThreads(for: contactID, in: transaction) let linkedDeviceThreads = LokiDatabaseUtilities.getLinkedDeviceThreads(for: contactID, in: transaction)
friendRequestStatuses = linkedDeviceThreads.map { thread in
storage.getFriendRequestStatus(forContact: thread.contactIdentifier(), transaction: transaction)
}
} }
// If the current user is friends with any of the other user's devices, the attachment button should be enabled // If the current user is friends with any of the other user's devices, the attachment button should be enabled
if linkedDeviceThreads.contains(where: { $0.isContactFriend }) { return true } if friendRequestStatuses.contains(where: { $0 == .friends }) { return true }
// If no friend request has been sent, the attachment button should be disabled // Otherwise don't allow attachments at all
if !linkedDeviceThreads.contains(where: { $0.hasPendingFriendRequest }) { return false }
// There must be a pending friend request
return false return false
} }
// MARK: - Sending // MARK: - Sending
@objc(acceptFriendRequestFrom:in:using:) @objc(acceptFriendRequestInThread:using:)
public static func acceptFriendRequest(from hexEncodedPublicKey: String, in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) { public static func acceptFriendRequest(in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
// Accept all outstanding friend requests associated with this user and try to establish sessions with the // Accept all outstanding friend requests associated with this user and try to establish sessions with the
// subset of their devices that haven't sent a friend request. // subset of their devices that haven't sent a friend request.
let linkedDeviceThreads = LokiDatabaseUtilities.getLinkedDeviceThreads(for: hexEncodedPublicKey, in: transaction) // This doesn't create new threads if they don't exist yet guard let thread = thread as? TSContactThread else { return }
for thread in linkedDeviceThreads {
if thread.hasPendingFriendRequest { let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: thread.contactIdentifier(), in: transaction)
sendFriendRequestAcceptanceMessage(to: thread.contactIdentifier(), in: thread, using: transaction) // NOT hexEncodedPublicKey for device in linkedDevices {
thread.saveFriendRequestStatus(.friends, with: transaction) let friendRequestStatus = storage.getFriendRequestStatus(forContact: device, transaction: transaction)
} else { if friendRequestStatus == .requestReceived {
let autoGeneratedFRMessageSend = MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessageSend(for: thread.contactIdentifier(), in: transaction) // NOT hexEncodedPublicKey storage.setFriendRequestStatus(.friends, forContact: device, transaction: transaction)
// TODO: Do we need to pass in `thread` here? If not then we can restructure this whole function to take in a hex encoded public key instead
sendFriendRequestAcceptanceMessage(to: device, in: thread, using: transaction)
} else if friendRequestStatus == .requestSent {
// We sent a friend request to this device before, how can we be sure that it hasn't expired?
} else if friendRequestStatus == .none || friendRequestStatus == .requestExpired {
// TODO: Need to track these so that we can expire them and resend incase the other user wasn't online after we sent
let autoGeneratedFRMessageSend = MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessageSend(for: device, in: transaction)
OWSDispatch.sendingQueue().async { OWSDispatch.sendingQueue().async {
let messageSender = SSKEnvironment.shared.messageSender let messageSender = SSKEnvironment.shared.messageSender
messageSender.sendMessage(autoGeneratedFRMessageSend) messageSender.sendMessage(autoGeneratedFRMessageSend)
@ -76,18 +92,28 @@ public final class FriendRequestProtocol : NSObject {
@objc(sendFriendRequestAcceptanceMessageToHexEncodedPublicKey:in:using:) @objc(sendFriendRequestAcceptanceMessageToHexEncodedPublicKey:in:using:)
public static func sendFriendRequestAcceptanceMessage(to hexEncodedPublicKey: String, in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) { public static func sendFriendRequestAcceptanceMessage(to hexEncodedPublicKey: String, in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
guard let thread = thread as? TSContactThread else { return }
let ephemeralMessage = EphemeralMessage(in: thread) let ephemeralMessage = EphemeralMessage(in: thread)
let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue
messageSenderJobQueue.add(message: ephemeralMessage, transaction: transaction) messageSenderJobQueue.add(message: ephemeralMessage, transaction: transaction)
} }
@objc(declineFriendRequest:in:using:) @objc(declineFriendRequestInThread:using:)
public static func declineFriendRequest(_ friendRequest: TSIncomingMessage, in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) { public static func declineFriendRequest(in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
thread.saveFriendRequestStatus(.none, with: transaction) guard let thread = thread as? TSContactThread else { return }
// Delete the pre key bundle for the given contact. This ensures that if we send a let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: thread.contactIdentifier(), in: transaction)
// new message after this, it restarts the friend request process from scratch. for device in linkedDevices {
let senderID = friendRequest.authorId let friendRequestStatus = storage.getFriendRequestStatus(forContact: device, transaction: transaction)
storage.removePreKeyBundle(forContact: senderID, transaction: transaction) // We only want to decline any incoming requests
assert(friendRequestStatus != .friends, "Invalid state transition. Cannot decline a friend request from a device we're already friends with. Thread: \(thread.uniqueId) - \(thread.contactIdentifier())")
if (friendRequestStatus == .requestReceived) {
// Delete the pre key bundle for the given contact. This ensures that if we send a
// new message after this, it restarts the friend request process from scratch.
storage.removePreKeyBundle(forContact: device, transaction: transaction)
storage.setFriendRequestStatus(.none, forContact: device, transaction: transaction)
}
}
} }
// MARK: - Receiving // MARK: - Receiving
@ -98,9 +124,9 @@ public final class FriendRequestProtocol : NSObject {
return (envelope.type == .friendRequest && envelope.timestamp < restorationTimeInMs) return (envelope.type == .friendRequest && envelope.timestamp < restorationTimeInMs)
} }
@objc(canFriendRequestBeAutoAcceptedForHexEncodedPublicKey:in:using:) @objc(canFriendRequestBeAutoAcceptedForHexEncodedPublicKey:using:)
public static func canFriendRequestBeAutoAccepted(for hexEncodedPublicKey: String, in thread: TSThread, using transaction: YapDatabaseReadTransaction) -> Bool { public static func canFriendRequestBeAutoAccepted(for hexEncodedPublicKey: String, using transaction: YapDatabaseReadTransaction) -> Bool {
if thread.hasCurrentUserSentFriendRequest { if storage.getFriendRequestStatus(forContact: hexEncodedPublicKey, transaction: transaction) == .requestSent {
// This can happen if Alice sent Bob a friend request, Bob declined, but then Bob changed his // This can happen if Alice sent Bob a friend request, Bob declined, but then Bob changed his
// mind and sent a friend request to Alice. In this case we want Alice to auto-accept the request // mind and sent a friend request to Alice. In this case we want Alice to auto-accept the request
// and send a friend request accepted message back to Bob. We don't check that sending the // and send a friend request accepted message back to Bob. We don't check that sending the
@ -117,8 +143,10 @@ public final class FriendRequestProtocol : NSObject {
let userLinkedDeviceHexEncodedPublicKeys = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction) let userLinkedDeviceHexEncodedPublicKeys = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction)
if userLinkedDeviceHexEncodedPublicKeys.contains(hexEncodedPublicKey) { return true } if userLinkedDeviceHexEncodedPublicKeys.contains(hexEncodedPublicKey) { return true }
// Auto-accept if the user is friends with any of the sender's linked devices. // Auto-accept if the user is friends with any of the sender's linked devices.
let senderLinkedDeviceThreads = LokiDatabaseUtilities.getLinkedDeviceThreads(for: hexEncodedPublicKey, in: transaction) let senderLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: hexEncodedPublicKey, in: transaction)
if senderLinkedDeviceThreads.contains(where: { $0.isContactFriend }) { return true } if senderLinkedDevices.contains(where: { storage.getFriendRequestStatus(forContact: $0, transaction: transaction) == .friends }) {
return true
}
// We can't auto-accept // We can't auto-accept
return false return false
} }
@ -163,8 +191,8 @@ public final class FriendRequestProtocol : NSObject {
print("[Loki] Ignoring friend request logic for non friend request type envelope.") print("[Loki] Ignoring friend request logic for non friend request type envelope.")
return return
} }
if canFriendRequestBeAutoAccepted(for: hexEncodedPublicKey, in: thread, using: transaction) { if canFriendRequestBeAutoAccepted(for: hexEncodedPublicKey, using: transaction) {
thread.saveFriendRequestStatus(.friends, with: transaction) storage.setFriendRequestStatus(.friends, forContact: hexEncodedPublicKey, transaction: transaction)
var existingFriendRequestMessage: TSOutgoingMessage? var existingFriendRequestMessage: TSOutgoingMessage?
thread.enumerateInteractions(with: transaction) { interaction, _ in thread.enumerateInteractions(with: transaction) { interaction, _ in
if let outgoingMessage = interaction as? TSOutgoingMessage, outgoingMessage.isFriendRequest { if let outgoingMessage = interaction as? TSOutgoingMessage, outgoingMessage.isFriendRequest {
@ -175,13 +203,13 @@ public final class FriendRequestProtocol : NSObject {
existingFriendRequestMessage.saveFriendRequestStatus(.accepted, with: transaction) existingFriendRequestMessage.saveFriendRequestStatus(.accepted, with: transaction)
} }
sendFriendRequestAcceptanceMessage(to: hexEncodedPublicKey, in: thread, using: transaction) sendFriendRequestAcceptanceMessage(to: hexEncodedPublicKey, in: thread, using: transaction)
} else if !thread.isContactFriend { } else if storage.getFriendRequestStatus(forContact: hexEncodedPublicKey, transaction: transaction) != .friends {
// Checking that the sender of the message isn't already a friend is necessary because otherwise // Checking that the sender of the message isn't already a friend is necessary because otherwise
// the following situation can occur: Alice and Bob are friends. Bob loses his database and his // the following situation can occur: Alice and Bob are friends. Bob loses his database and his
// friend request status is reset to LKThreadFriendRequestStatusNone. Bob now sends Alice a friend // friend request status is reset to LKThreadFriendRequestStatusNone. Bob now sends Alice a friend
// request. Alice's thread's friend request status is reset to // request. Alice's thread's friend request status is reset to
// LKThreadFriendRequestStatusRequestReceived. // LKThreadFriendRequestStatusRequestReceived.
thread.saveFriendRequestStatus(.requestReceived, with: transaction) storage.setFriendRequestStatus(.requestReceived, forContact: hexEncodedPublicKey, transaction: transaction)
// Except for the message.friendRequestStatus = LKMessageFriendRequestStatusPending line below, all of this is to ensure that // Except for the message.friendRequestStatus = LKMessageFriendRequestStatusPending line below, all of this is to ensure that
// there's only ever one message with status LKMessageFriendRequestStatusPending in a thread (where a thread is the combination // there's only ever one message with status LKMessageFriendRequestStatusPending in a thread (where a thread is the combination
// of all threads belonging to the linked devices of a user). // of all threads belonging to the linked devices of a user).

@ -196,7 +196,7 @@ public final class SyncMessagesProtocol : NSObject {
case .requestReceived: case .requestReceived:
storage.setFriendRequestStatus(.friends, forContact: hexEncodedPublicKey, transaction: transaction) storage.setFriendRequestStatus(.friends, forContact: hexEncodedPublicKey, transaction: transaction)
// Not sendFriendRequestAcceptanceMessage(to:in:using:) to take into account multi device // Not sendFriendRequestAcceptanceMessage(to:in:using:) to take into account multi device
FriendRequestProtocol.acceptFriendRequest(from: hexEncodedPublicKey, in: thread, using: transaction) FriendRequestProtocol.acceptFriendRequest(in: thread, using: transaction)
default: break default: break
} }
} }

Loading…
Cancel
Save