Refactor friend request protocol

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

@ -4466,14 +4466,14 @@ typedef enum : NSUInteger {
- (void)acceptFriendRequest:(TSIncomingMessage *)friendRequest
{
[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
{
[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() }
// Mark: - Status
private static func isPendingFriendRequest(_ status: LKFriendRequestStatus) -> Bool {
return status == .requestSending || status == .requestSent || status == .requestReceived
}
// MARK: - General
@objc(shouldInputBarBeEnabledForThread:)
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 SessionProtocol.isMessageNoteToSelf(thread) { return true }
let contactID = thread.contactIdentifier()
var linkedDeviceThreads: Set<TSContactThread> = []
var friendRequestStatuses: [LKFriendRequestStatus] = []
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 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 !linkedDeviceThreads.contains(where: { $0.hasPendingFriendRequest }) { return true }
if !friendRequestStatuses.contains(where: { isPendingFriendRequest($0) }) { return true }
// There must be a pending friend request
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 SessionProtocol.isMessageNoteToSelf(thread) { return true }
let contactID = thread.contactIdentifier()
var linkedDeviceThreads: Set<TSContactThread> = []
var friendRequestStatuses: [LKFriendRequestStatus] = []
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 linkedDeviceThreads.contains(where: { $0.isContactFriend }) { return true }
// If no friend request has been sent, the attachment button should be disabled
if !linkedDeviceThreads.contains(where: { $0.hasPendingFriendRequest }) { return false }
// There must be a pending friend request
if friendRequestStatuses.contains(where: { $0 == .friends }) { return true }
// Otherwise don't allow attachments at all
return false
}
// MARK: - Sending
@objc(acceptFriendRequestFrom:in:using:)
public static func acceptFriendRequest(from hexEncodedPublicKey: String, in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
@objc(acceptFriendRequestInThread:using:)
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
// 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
for thread in linkedDeviceThreads {
if thread.hasPendingFriendRequest {
sendFriendRequestAcceptanceMessage(to: thread.contactIdentifier(), in: thread, using: transaction) // NOT hexEncodedPublicKey
thread.saveFriendRequestStatus(.friends, with: transaction)
} else {
let autoGeneratedFRMessageSend = MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessageSend(for: thread.contactIdentifier(), in: transaction) // NOT hexEncodedPublicKey
guard let thread = thread as? TSContactThread else { return }
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: thread.contactIdentifier(), in: transaction)
for device in linkedDevices {
let friendRequestStatus = storage.getFriendRequestStatus(forContact: device, transaction: transaction)
if friendRequestStatus == .requestReceived {
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 {
let messageSender = SSKEnvironment.shared.messageSender
messageSender.sendMessage(autoGeneratedFRMessageSend)
@ -76,18 +92,28 @@ public final class FriendRequestProtocol : NSObject {
@objc(sendFriendRequestAcceptanceMessageToHexEncodedPublicKey:in:using:)
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 messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue
messageSenderJobQueue.add(message: ephemeralMessage, transaction: transaction)
}
@objc(declineFriendRequest:in:using:)
public static func declineFriendRequest(_ friendRequest: TSIncomingMessage, in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
thread.saveFriendRequestStatus(.none, with: transaction)
// 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.
let senderID = friendRequest.authorId
storage.removePreKeyBundle(forContact: senderID, transaction: transaction)
@objc(declineFriendRequestInThread:using:)
public static func declineFriendRequest(in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
guard let thread = thread as? TSContactThread else { return }
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: thread.contactIdentifier(), in: transaction)
for device in linkedDevices {
let friendRequestStatus = storage.getFriendRequestStatus(forContact: device, 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
@ -98,9 +124,9 @@ public final class FriendRequestProtocol : NSObject {
return (envelope.type == .friendRequest && envelope.timestamp < restorationTimeInMs)
}
@objc(canFriendRequestBeAutoAcceptedForHexEncodedPublicKey:in:using:)
public static func canFriendRequestBeAutoAccepted(for hexEncodedPublicKey: String, in thread: TSThread, using transaction: YapDatabaseReadTransaction) -> Bool {
if thread.hasCurrentUserSentFriendRequest {
@objc(canFriendRequestBeAutoAcceptedForHexEncodedPublicKey:using:)
public static func canFriendRequestBeAutoAccepted(for hexEncodedPublicKey: String, using transaction: YapDatabaseReadTransaction) -> Bool {
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
// 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
@ -117,8 +143,10 @@ public final class FriendRequestProtocol : NSObject {
let userLinkedDeviceHexEncodedPublicKeys = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction)
if userLinkedDeviceHexEncodedPublicKeys.contains(hexEncodedPublicKey) { return true }
// Auto-accept if the user is friends with any of the sender's linked devices.
let senderLinkedDeviceThreads = LokiDatabaseUtilities.getLinkedDeviceThreads(for: hexEncodedPublicKey, in: transaction)
if senderLinkedDeviceThreads.contains(where: { $0.isContactFriend }) { return true }
let senderLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: hexEncodedPublicKey, in: transaction)
if senderLinkedDevices.contains(where: { storage.getFriendRequestStatus(forContact: $0, transaction: transaction) == .friends }) {
return true
}
// We can't auto-accept
return false
}
@ -163,8 +191,8 @@ public final class FriendRequestProtocol : NSObject {
print("[Loki] Ignoring friend request logic for non friend request type envelope.")
return
}
if canFriendRequestBeAutoAccepted(for: hexEncodedPublicKey, in: thread, using: transaction) {
thread.saveFriendRequestStatus(.friends, with: transaction)
if canFriendRequestBeAutoAccepted(for: hexEncodedPublicKey, using: transaction) {
storage.setFriendRequestStatus(.friends, forContact: hexEncodedPublicKey, transaction: transaction)
var existingFriendRequestMessage: TSOutgoingMessage?
thread.enumerateInteractions(with: transaction) { interaction, _ in
if let outgoingMessage = interaction as? TSOutgoingMessage, outgoingMessage.isFriendRequest {
@ -175,13 +203,13 @@ public final class FriendRequestProtocol : NSObject {
existingFriendRequestMessage.saveFriendRequestStatus(.accepted, with: 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
// 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
// request. Alice's thread's friend request status is reset to
// 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
// 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).

@ -196,7 +196,7 @@ public final class SyncMessagesProtocol : NSObject {
case .requestReceived:
storage.setFriendRequestStatus(.friends, forContact: hexEncodedPublicKey, transaction: transaction)
// 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
}
}

Loading…
Cancel
Save