@ -15,6 +15,11 @@ public final class FriendRequestProtocol : NSObject {
internal static var storage : OWSPrimaryStorage { OWSPrimaryStorage . shared ( ) }
internal static var storage : OWSPrimaryStorage { OWSPrimaryStorage . shared ( ) }
// M a r k : - S t a t u s
private static func isPendingFriendRequest ( _ status : LKFriendRequestStatus ) -> Bool {
return status = = . requestSending || status = = . requestSent || status = = . requestReceived
}
// MARK: - G e n e r a l
// MARK: - G e n e r a l
@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 {
// I f t h i s i s a n o t e t o s e l f , t h e i n p u t b a r s h o u l d b e e n a b l e d
// I f t h i s i s a n o t e t o s e l f , t h e i n p u t b a r s h o u l d b e e n a b l e d
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 )
}
}
}
// I f t h e c u r r e n t u s e r i s f r i e n d s w i t h a n y o f t h e o t h e r u s e r ' s d e v i c e s , t h e i n p u t b a r s h o u l d b e e n a b l e d
// I f t h e c u r r e n t u s e r i s f r i e n d s w i t h a n y o f t h e o t h e r u s e r ' s d e v i c e s , t h e i n p u t b a r s h o u l d b e e n a b l e d
if linkedDeviceThreads . contains ( where : { $0 . isContactFriend } ) { return true }
if friendRequestStatuses. contains ( where : { $0 = = . friends } ) { return true }
// I f n o f r i e n d r e q u e s t h a s b e e n s e n t , t h e i n p u t b a r s h o u l d b e e n a b l e d
// I f n o f r i e n d r e q u e s t h a s b e e n s e n t , t h e i n p u t b a r s h o u l d b e e n a b l e d
if ! linkedDeviceThreads. contains ( where : { $0 . hasPendingFriendRequest } ) { return true }
if ! friendRequestStatuses. contains ( where : { isPendingFriendRequest ( $0 ) } ) { return true }
// T h e r e m u s t b e a p e n d i n g f r i e n d r e q u e s t
// T h e r e m u s t b e a p e n d i n g f r i e n d r e q u e s t
return false
return false
}
}
@ -42,30 +50,38 @@ public final class FriendRequestProtocol : NSObject {
// I f t h i s i s a n o t e t o s e l f , t h e a t t a c h m e n t b u t t o n s h o u l d b e e n a b l e d
// I f t h i s i s a n o t e t o s e l f , t h e a t t a c h m e n t b u t t o n s h o u l d b e e n a b l e d
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 )
}
}
}
// I f t h e c u r r e n t u s e r i s f r i e n d s w i t h a n y o f t h e o t h e r u s e r ' s d e v i c e s , t h e a t t a c h m e n t b u t t o n s h o u l d b e e n a b l e d
// I f t h e c u r r e n t u s e r i s f r i e n d s w i t h a n y o f t h e o t h e r u s e r ' s d e v i c e s , t h e a t t a c h m e n t b u t t o n s h o u l d b e e n a b l e d
if linkedDeviceThreads . contains ( where : { $0 . isContactFriend } ) { return true }
if friendRequestStatuses . contains ( where : { $0 = = . friends } ) { return true }
// I f n o f r i e n d r e q u e s t h a s b e e n s e n t , t h e a t t a c h m e n t b u t t o n s h o u l d b e d i s a b l e d
// O t h e r w i s e d o n ' t a l l o w a t t a c h m e n t s a t a l l
if ! linkedDeviceThreads . contains ( where : { $0 . hasPendingFriendRequest } ) { return false }
// T h e r e m u s t b e a p e n d i n g f r i e n d r e q u e s t
return false
return false
}
}
// MARK: - S e n d i n g
// MARK: - S e n d i n g
@objc ( acceptFriendRequest From: in : using : )
@objc ( acceptFriendRequest InThread : using : )
public static func acceptFriendRequest ( from hexEncodedPublicKey : String , in thread : TSThread , using transaction : YapDatabaseReadWriteTransaction ) {
public static func acceptFriendRequest ( in thread : TSThread , using transaction : YapDatabaseReadWriteTransaction ) {
// A c c e p t a l l o u t s t a n d i n g f r i e n d r e q u e s t s a s s o c i a t e d w i t h t h i s u s e r a n d t r y t o e s t a b l i s h s e s s i o n s w i t h t h e
// A c c e p t a l l o u t s t a n d i n g f r i e n d r e q u e s t s a s s o c i a t e d w i t h t h i s u s e r a n d t r y t o e s t a b l i s h s e s s i o n s w i t h t h e
// s u b s e t o f t h e i r d e v i c e s t h a t h a v e n ' t s e n t a f r i e n d r e q u e s t .
// s u b s e t o f t h e i r d e v i c e s t h a t h a v e n ' t s e n t a f r i e n d r e q u e s t .
let linkedDeviceThreads = LokiDatabaseUtilities . getLinkedDeviceThreads ( for : hexEncodedPublicKey , in : transaction ) // T h i s d o e s n ' t c r e a t e n e w t h r e a d s i f t h e y d o n ' t e x i s t y e t
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 ) // N O T h e x E n c o d e d P u b l i c K e y
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 ) // N O T h e x E n c o d e d P u b l i c K e y
storage . setFriendRequestStatus ( . friends , forContact : device , transaction : transaction )
// TODO: D o w e n e e d t o p a s s i n ` t h r e a d ` h e r e ? I f n o t t h e n w e c a n r e s t r u c t u r e t h i s w h o l e f u n c t i o n t o t a k e i n a h e x e n c o d e d p u b l i c k e y i n s t e a d
sendFriendRequestAcceptanceMessage ( to : device , in : thread , using : transaction )
} else if friendRequestStatus = = . requestSent {
// W e s e n t a f r i e n d r e q u e s t t o t h i s d e v i c e b e f o r e , h o w c a n w e b e s u r e t h a t i t h a s n ' t e x p i r e d ?
} else if friendRequestStatus = = . none || friendRequestStatus = = . requestExpired {
// TODO: N e e d t o t r a c k t h e s e s o t h a t w e c a n e x p i r e t h e m a n d r e s e n d i n c a s e t h e o t h e r u s e r w a s n ' t o n l i n e a f t e r w e s e n t
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 }
// D e l e t e t h e p r e k e y b u n d l e f o r t h e g i v e n c o n t a c t . T h i s e n s u r e s t h a t i f w e s e n d a
let linkedDevices = LokiDatabaseUtilities . getLinkedDeviceHexEncodedPublicKeys ( for : thread . contactIdentifier ( ) , in : transaction )
// n e w m e s s a g e a f t e r t h i s , i t r e s t a r t s t h e f r i e n d r e q u e s t p r o c e s s f r o m s c r a t c h .
for device in linkedDevices {
let senderID = friendRequest . authorId
let friendRequestStatus = storage . getFriendRequestStatus ( forContact : device , transaction : transaction )
storage . removePreKeyBundle ( forContact : senderID , transaction : transaction )
// W e o n l y w a n t t o d e c l i n e a n y i n c o m i n g r e q u e s t s
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 ) {
// D e l e t e t h e p r e k e y b u n d l e f o r t h e g i v e n c o n t a c t . T h i s e n s u r e s t h a t i f w e s e n d a
// n e w m e s s a g e a f t e r t h i s , i t r e s t a r t s t h e f r i e n d r e q u e s t p r o c e s s f r o m s c r a t c h .
storage . removePreKeyBundle ( forContact : device , transaction : transaction )
storage . setFriendRequestStatus ( . none , forContact : device , transaction : transaction )
}
}
}
}
// MARK: - R e c e i v i n g
// MARK: - R e c e i v i n g
@ -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. hasCurrentUserSentFriendReques t {
if storage. getFriendRequestStatus ( forContact : hexEncodedPublicKey , transaction : transaction ) = = . requestSen t {
// T h i s c a n h a p p e n i f A l i c e s e n t B o b a f r i e n d r e q u e s t , B o b d e c l i n e d , b u t t h e n B o b c h a n g e d h i s
// T h i s c a n h a p p e n i f A l i c e s e n t B o b a f r i e n d r e q u e s t , B o b d e c l i n e d , b u t t h e n B o b c h a n g e d h i s
// m i n d a n d s e n t a f r i e n d r e q u e s t t o A l i c e . I n t h i s c a s e w e w a n t A l i c e t o a u t o - a c c e p t t h e r e q u e s t
// m i n d a n d s e n t a f r i e n d r e q u e s t t o A l i c e . I n t h i s c a s e w e w a n t A l i c e t o a u t o - a c c e p t t h e r e q u e s t
// a n d s e n d a f r i e n d r e q u e s t a c c e p t e d m e s s a g e b a c k t o B o b . W e d o n ' t c h e c k t h a t s e n d i n g t h e
// a n d s e n d a f r i e n d r e q u e s t a c c e p t e d m e s s a g e b a c k t o B o b . W e d o n ' t c h e c k t h a t s e n d i n g t h e
@ -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 }
// A u t o - a c c e p t i f t h e u s e r i s f r i e n d s w i t h a n y o f t h e s e n d e r ' s l i n k e d d e v i c e s .
// A u t o - a c c e p t i f t h e u s e r i s f r i e n d s w i t h a n y o f t h e s e n d e r ' s l i n k e d d e v i c e s .
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
}
// W e c a n ' t a u t o - a c c e p t
// W e c a n ' t a u t o - a c c e p t
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 {
// C h e c k i n g t h a t t h e s e n d e r o f t h e m e s s a g e i s n ' t a l r e a d y a f r i e n d i s n e c e s s a r y b e c a u s e o t h e r w i s e
// C h e c k i n g t h a t t h e s e n d e r o f t h e m e s s a g e i s n ' t a l r e a d y a f r i e n d i s n e c e s s a r y b e c a u s e o t h e r w i s e
// t h e f o l l o w i n g s i t u a t i o n c a n o c c u r : A l i c e a n d B o b a r e f r i e n d s . B o b l o s e s h i s d a t a b a s e a n d h i s
// t h e f o l l o w i n g s i t u a t i o n c a n o c c u r : A l i c e a n d B o b a r e f r i e n d s . B o b l o s e s h i s d a t a b a s e a n d h i s
// f r i e n d r e q u e s t s t a t u s i s r e s e t t o L K T h r e a d F r i e n d R e q u e s t S t a t u s N o n e . B o b n o w s e n d s A l i c e a f r i e n d
// f r i e n d r e q u e s t s t a t u s i s r e s e t t o L K T h r e a d F r i e n d R e q u e s t S t a t u s N o n e . B o b n o w s e n d s A l i c e a f r i e n d
// r e q u e s t . A l i c e ' s t h r e a d ' s f r i e n d r e q u e s t s t a t u s i s r e s e t t o
// r e q u e s t . A l i c e ' s t h r e a d ' s f r i e n d r e q u e s t s t a t u s i s r e s e t t o
// L K T h r e a d F r i e n d R e q u e s t S t a t u s R e q u e s t R e c e i v e d .
// L K T h r e a d F r i e n d R e q u e s t S t a t u s R e q u e s t R e c e i v e d .
thread. saveFriendRequestStatus ( . requestReceived , with : transaction )
storage. setFriendRequestStatus ( . requestReceived , forContact : hexEncodedPublicKey , transaction : transaction )
// E x c e p t f o r t h e m e s s a g e . f r i e n d R e q u e s t S t a t u s = L K M e s s a g e F r i e n d R e q u e s t S t a t u s P e n d i n g l i n e b e l o w , a l l o f t h i s i s t o e n s u r e t h a t
// E x c e p t f o r t h e m e s s a g e . f r i e n d R e q u e s t S t a t u s = L K M e s s a g e F r i e n d R e q u e s t S t a t u s P e n d i n g l i n e b e l o w , a l l o f t h i s i s t o e n s u r e t h a t
// t h e r e ' s o n l y e v e r o n e m e s s a g e w i t h s t a t u s L K M e s s a g e F r i e n d R e q u e s t S t a t u s P e n d i n g i n a t h r e a d ( w h e r e a t h r e a d i s t h e c o m b i n a t i o n
// t h e r e ' s o n l y e v e r o n e m e s s a g e w i t h s t a t u s L K M e s s a g e F r i e n d R e q u e s t S t a t u s P e n d i n g i n a t h r e a d ( w h e r e a t h r e a d i s t h e c o m b i n a t i o n
// o f a l l t h r e a d s b e l o n g i n g t o t h e l i n k e d d e v i c e s o f a u s e r ) .
// o f a l l t h r e a d s b e l o n g i n g t o t h e l i n k e d d e v i c e s o f a u s e r ) .