|
|
|
@ -15,44 +15,43 @@ public final class FriendRequestProtocol : NSObject {
|
|
|
|
|
|
|
|
|
|
internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() }
|
|
|
|
|
|
|
|
|
|
// MARK: - Enums
|
|
|
|
|
// FIXME: Better naming :(
|
|
|
|
|
@objc public enum FriendRequestUIState : Int {
|
|
|
|
|
// MARK: - Friend Request UI Status
|
|
|
|
|
@objc public enum FriendRequestUIStatus : Int {
|
|
|
|
|
case friends, received, sent, none
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - General
|
|
|
|
|
@objc(isFriendsWithLinkedDevicesOfHexEncodedPublicKey:)
|
|
|
|
|
public static func isFriendsWithLinkedDevices(of hexEncodedPublicKey: String) -> Bool {
|
|
|
|
|
var isFriends = false
|
|
|
|
|
@objc(isFriendsWithAnyLinkedDeviceOfHexEncodedPublicKey:)
|
|
|
|
|
public static func isFriendsWithAnyLinkedDevice(of hexEncodedPublicKey: String) -> Bool {
|
|
|
|
|
var result = false
|
|
|
|
|
storage.dbReadConnection.read { transaction in
|
|
|
|
|
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: hexEncodedPublicKey, in: transaction)
|
|
|
|
|
let friendRequestStatuses = linkedDevices.map { device in
|
|
|
|
|
return storage.getFriendRequestStatus(for: device, transaction: transaction)
|
|
|
|
|
let friendRequestStatuses = linkedDevices.map {
|
|
|
|
|
storage.getFriendRequestStatus(for: $0, transaction: transaction)
|
|
|
|
|
}
|
|
|
|
|
isFriends = friendRequestStatuses.contains(where: { $0 == .friends })
|
|
|
|
|
result = friendRequestStatuses.contains { $0 == .friends }
|
|
|
|
|
}
|
|
|
|
|
return isFriends
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc(shouldInputBarBeEnabledForThread:)
|
|
|
|
|
public static func shouldInputBarBeEnabled(for thread: TSThread) -> Bool {
|
|
|
|
|
// 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 }
|
|
|
|
|
// 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 thread.isNoteToSelf() { return true }
|
|
|
|
|
let contactID = thread.contactIdentifier()
|
|
|
|
|
var friendRequestStatuses: [LKFriendRequestStatus] = []
|
|
|
|
|
var linkedDeviceFriendRequestStatuses: [LKFriendRequestStatus] = []
|
|
|
|
|
storage.dbReadConnection.read { transaction in
|
|
|
|
|
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: contactID, in: transaction)
|
|
|
|
|
friendRequestStatuses = linkedDevices.map { device in
|
|
|
|
|
return storage.getFriendRequestStatus(for: device, transaction: transaction)
|
|
|
|
|
linkedDeviceFriendRequestStatuses = linkedDevices.map {
|
|
|
|
|
storage.getFriendRequestStatus(for: $0, transaction: transaction)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// If the current user is friends with any of the other user's devices, the input bar should be enabled
|
|
|
|
|
if friendRequestStatuses.contains(where: { $0 == .friends }) { return true }
|
|
|
|
|
if linkedDeviceFriendRequestStatuses.contains(where: { $0 == .friends }) { return true }
|
|
|
|
|
// If no friend request has been sent, the input bar should be enabled
|
|
|
|
|
if friendRequestStatuses.allSatisfy({ $0 == .none || $0 == .requestExpired }) { return true }
|
|
|
|
|
if linkedDeviceFriendRequestStatuses.allSatisfy({ $0 == .none || $0 == .requestExpired }) { return true }
|
|
|
|
|
// There must be a pending friend request
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
@ -64,50 +63,47 @@ public final class FriendRequestProtocol : NSObject {
|
|
|
|
|
// If this is a note to self, the attachment button should be enabled
|
|
|
|
|
if thread.isNoteToSelf() { return true }
|
|
|
|
|
let contactID = thread.contactIdentifier()
|
|
|
|
|
var friendRequestStatuses: [LKFriendRequestStatus] = []
|
|
|
|
|
var linkedDeviceFriendRequestStatuses: [LKFriendRequestStatus] = []
|
|
|
|
|
storage.dbReadConnection.read { transaction in
|
|
|
|
|
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: contactID, in: transaction)
|
|
|
|
|
friendRequestStatuses = linkedDevices.map { device in
|
|
|
|
|
storage.getFriendRequestStatus(for: device, transaction: transaction)
|
|
|
|
|
linkedDeviceFriendRequestStatuses = linkedDevices.map {
|
|
|
|
|
storage.getFriendRequestStatus(for: $0, transaction: transaction)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// If the current user is friends with any of the other user's devices, the attachment button should be enabled
|
|
|
|
|
if friendRequestStatuses.contains(where: { $0 == .friends }) { return true }
|
|
|
|
|
// Otherwise don't allow attachments at all
|
|
|
|
|
if linkedDeviceFriendRequestStatuses.contains(where: { $0 == .friends }) { return true }
|
|
|
|
|
// Otherwise don't allow attachments
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc(getFriendRequestUIStateForThread:)
|
|
|
|
|
public static func getFriendRequestUIState(for thread: TSThread) -> FriendRequestUIState {
|
|
|
|
|
@objc(getFriendRequestUIStatusForThread:)
|
|
|
|
|
public static func getFriendRequestUIStatus(for thread: TSThread) -> FriendRequestUIStatus {
|
|
|
|
|
// Friend requests have nothing to do with groups
|
|
|
|
|
guard let thread = thread as? TSContactThread else { return .none }
|
|
|
|
|
// If this is a note to self then we don't want to show the friend request
|
|
|
|
|
if thread.isNoteToSelf() { return .none }
|
|
|
|
|
guard !thread.isNoteToSelf() else { return .none }
|
|
|
|
|
var friendRequestStatuses: [LKFriendRequestStatus] = []
|
|
|
|
|
storage.dbReadConnection.read { transaction in
|
|
|
|
|
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: thread.contactIdentifier(), in: transaction)
|
|
|
|
|
friendRequestStatuses = linkedDevices.map { device in
|
|
|
|
|
return storage.getFriendRequestStatus(for: device, transaction: transaction)
|
|
|
|
|
friendRequestStatuses = linkedDevices.map {
|
|
|
|
|
storage.getFriendRequestStatus(for: $0, transaction: transaction)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if friendRequestStatuses.contains(where: { $0 == .friends }) { return .friends }
|
|
|
|
|
if friendRequestStatuses.contains(where: { $0 == .requestReceived }) { return .received }
|
|
|
|
|
if friendRequestStatuses.contains(where: { $0 == .requestSent }) { return .sent }
|
|
|
|
|
|
|
|
|
|
return .none
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - Sending
|
|
|
|
|
@objc(acceptFriendRequestFromHexEncodedPublicKey:using:)
|
|
|
|
|
public static func acceptFriendRequest(from hexEncodedPublicKey: String, 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.
|
|
|
|
|
guard ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) else {
|
|
|
|
|
assertionFailure("Invalid session ID \(hexEncodedPublicKey)")
|
|
|
|
|
return;
|
|
|
|
|
print("[Loki] Invalid Session ID: \(hexEncodedPublicKey).")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: hexEncodedPublicKey, in: transaction)
|
|
|
|
|
for device in linkedDevices {
|
|
|
|
|
let friendRequestStatus = storage.getFriendRequestStatus(for: device, transaction: transaction)
|
|
|
|
@ -116,12 +112,13 @@ public final class FriendRequestProtocol : NSObject {
|
|
|
|
|
sendFriendRequestAcceptanceMessage(to: device, 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?
|
|
|
|
|
// TODO: Shouldn't the status then be .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
|
|
|
|
|
MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessageSend(for: device, in: transaction) // NOT hexEncodedPublicKey
|
|
|
|
|
.done(on: OWSDispatch.sendingQueue()) { autoGeneratedFRMessageSend in
|
|
|
|
|
let messageSender = SSKEnvironment.shared.messageSender
|
|
|
|
|
messageSender.sendMessage(autoGeneratedFRMessageSend)
|
|
|
|
|
// TODO: We should track these so that we can expire them and resend if needed
|
|
|
|
|
MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessageSend(for: device, in: transaction)
|
|
|
|
|
.done(on: OWSDispatch.sendingQueue()) { autoGeneratedFRMessageSend in
|
|
|
|
|
let messageSender = SSKEnvironment.shared.messageSender
|
|
|
|
|
messageSender.sendMessage(autoGeneratedFRMessageSend)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -130,10 +127,9 @@ public final class FriendRequestProtocol : NSObject {
|
|
|
|
|
@objc(sendFriendRequestAcceptanceMessageToHexEncodedPublicKey:using:)
|
|
|
|
|
public static func sendFriendRequestAcceptanceMessage(to hexEncodedPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
guard ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) else {
|
|
|
|
|
assertionFailure("Invalid session ID \(hexEncodedPublicKey)")
|
|
|
|
|
return;
|
|
|
|
|
print("[Loki] Invalid Session ID: \(hexEncodedPublicKey).")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
let ephemeralMessage = EphemeralMessage(in: thread)
|
|
|
|
|
let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue
|
|
|
|
@ -143,15 +139,14 @@ public final class FriendRequestProtocol : NSObject {
|
|
|
|
|
@objc(declineFriendRequestFromHexEncodedPublicKey:using:)
|
|
|
|
|
public static func declineFriendRequest(from hexEncodedPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
guard ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) else {
|
|
|
|
|
assertionFailure("Invalid session ID \(hexEncodedPublicKey)")
|
|
|
|
|
return;
|
|
|
|
|
print("[Loki] Invalid Session ID: \(hexEncodedPublicKey).")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: hexEncodedPublicKey, in: transaction)
|
|
|
|
|
for device in linkedDevices {
|
|
|
|
|
let friendRequestStatus = storage.getFriendRequestStatus(for: device, transaction: transaction)
|
|
|
|
|
// We only want to decline any incoming requests
|
|
|
|
|
if (friendRequestStatus == .requestReceived) {
|
|
|
|
|
// We only want to decline incoming requests
|
|
|
|
|
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)
|
|
|
|
@ -160,28 +155,25 @@ public final class FriendRequestProtocol : NSObject {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc(sendingFriendRequestToHexEncodedPublicKey:transaction:)
|
|
|
|
|
public static func sendingFriendRequest(to hexEncodedPublicKey: String, transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
@objc(setFriendRequestStatusToSendingIfNeededForHexEncodedPublicKey:transaction:)
|
|
|
|
|
public static func setFriendRequestStatusToSendingIfNeeded(for hexEncodedPublicKey: String, transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
let friendRequestStatus = storage.getFriendRequestStatus(for: hexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
if (friendRequestStatus == .none || friendRequestStatus == .requestExpired) {
|
|
|
|
|
storage.setFriendRequestStatus(.requestSending, for: hexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
}
|
|
|
|
|
guard friendRequestStatus == .none || friendRequestStatus == .requestExpired else { return }
|
|
|
|
|
storage.setFriendRequestStatus(.requestSending, for: hexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc(sentFriendRequestToHexEncodedPublicKey:transaction:)
|
|
|
|
|
public static func sentFriendRequest(to hexEncodedPublicKey: String, transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
@objc(setFriendRequestStatusToSentIfNeededForHexEncodedPublicKey:transaction:)
|
|
|
|
|
public static func setFriendRequestStatusToSentIfNeeded(for hexEncodedPublicKey: String, transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
let friendRequestStatus = storage.getFriendRequestStatus(for: hexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
if (friendRequestStatus == .none || friendRequestStatus == .requestExpired || friendRequestStatus == .requestSending) {
|
|
|
|
|
storage.setFriendRequestStatus(.requestSent, for: hexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
}
|
|
|
|
|
guard friendRequestStatus == .none || friendRequestStatus == .requestExpired || friendRequestStatus == .requestSending else { return }
|
|
|
|
|
storage.setFriendRequestStatus(.requestSent, for: hexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc(failedToSendFriendRequestToHexEncodedPublicKey:transaction:)
|
|
|
|
|
public static func failedToSendFriendRequest(to hexEncodedPublicKey: String, transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
@objc(setFriendRequestStatusToFailedIfNeededForHexEncodedPublicKey:transaction:)
|
|
|
|
|
public static func setFriendRequestStatusToFailedIfNeeded(for hexEncodedPublicKey: String, transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
let friendRequestStatus = storage.getFriendRequestStatus(for: hexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
if (friendRequestStatus == .requestSending) {
|
|
|
|
|
storage.setFriendRequestStatus(.none, for: hexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
}
|
|
|
|
|
guard friendRequestStatus == .requestSending else { return }
|
|
|
|
|
storage.setFriendRequestStatus(.none, for: hexEncodedPublicKey, transaction: transaction)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - Receiving
|
|
|
|
@ -212,7 +204,9 @@ public final class FriendRequestProtocol : NSObject {
|
|
|
|
|
if userLinkedDeviceHexEncodedPublicKeys.contains(hexEncodedPublicKey) { return true }
|
|
|
|
|
// Auto-accept if the user is friends with any of the sender's linked devices.
|
|
|
|
|
let senderLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: hexEncodedPublicKey, in: transaction)
|
|
|
|
|
if senderLinkedDevices.contains(where: { storage.getFriendRequestStatus(for: $0, transaction: transaction) == .friends }) {
|
|
|
|
|
if senderLinkedDevices.contains(where: {
|
|
|
|
|
storage.getFriendRequestStatus(for: $0, transaction: transaction) == .friends
|
|
|
|
|
}) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
// We can't auto-accept
|
|
|
|
|