@ -29,9 +29,7 @@ public final class MultiDeviceProtocol : NSObject {
// MARK: - M u l t i D e v i c e D e s t i n a t i o n
public struct MultiDeviceDestination : Hashable {
public let hexEncodedPublicKey : String
public let kind : Kind
public enum Kind : String { case master , slave }
public let isMaster : Bool
}
// MARK: - I n i t i a l i z a t i o n
@ -40,65 +38,81 @@ public final class MultiDeviceProtocol : NSObject {
// MARK: - S e n d i n g ( P a r t 1 )
@objc ( isMultiDeviceRequiredForMessage : )
public static func isMultiDeviceRequired ( for message : TSOutgoingMessage ) -> Bool {
return ! ( message is DeviceLinkMessage )
return ! ( message is DeviceLinkMessage ) && ! message . thread . isGroupThread ( )
}
@objc ( sendMessageToDestinationAndLinkedDevices : in : )
public static func sendMessageToDestinationAndLinkedDevices ( _ messageSend : OWSMessageSend , in transaction : YapDatabaseReadWriteTransaction ) {
// TODO: I ' m p r e t t y s u r e t h e r e a r e q u i t e a f e w h o l e s i n t h i s l o g i c
private static func sendMessage ( _ messageSend : OWSMessageSend , to destination : MultiDeviceDestination , in transaction : YapDatabaseReadTransaction ) {
let ( promise , seal ) = Promise < TSContactThread > . pending ( )
let message = messageSend . message
let recipientID = messageSend . recipient . recipientId ( )
let thread = messageSend . thread ? ? TSContactThread . getOrCreateThread ( withContactId : recipientID , transaction : transaction ) // TODO: T h i s s e e m s r e a l l y i f f y
let isGroupMessage = thread . isGroupThread ( )
let isOpenGroupMessage = ( thread as ? TSGroupThread ) ? . isPublicChat = = true
let isDeviceLinkMessage = message is DeviceLinkMessage
let messageSender = SSKEnvironment . shared . messageSender
guard ! isOpenGroupMessage && ! isDeviceLinkMessage else {
return messageSender . sendMessage ( messageSend )
}
let isSilentMessage = message . isSilent || message is EphemeralMessage || message is OWSOutgoingSyncMessage
let isFriendRequestMessage = message is FriendRequestMessage
let isSessionRequestMessage = message is SessionRequestMessage
getMultiDeviceDestinations ( for : recipientID , in : transaction ) . done ( on : OWSDispatch . sendingQueue ( ) ) { destinations in
// S e n d t o m a s t e r d e s t i n a t i o n
if let masterDestination = destinations . first ( where : { $0 . kind = = . master } ) {
let thread = TSContactThread . getOrCreateThread ( contactId : masterDestination . hexEncodedPublicKey ) // TODO: I g u e s s i t ' s o k a y t h i s s t a r t s a n e w t r a n s a c t i o n ?
if thread . isContactFriend || isSilentMessage || isFriendRequestMessage || isSessionRequestMessage || isGroupMessage {
let messageSendCopy = messageSend . copy ( with : masterDestination )
promise . done ( on : OWSDispatch . sendingQueue ( ) ) { thread in
let isSessionResetMessage = ( message is EphemeralMessage ) && thread . sessionResetStatus = = . requestReceived
let shouldSendAutoGeneratedFR = ! thread . isContactFriend && ! ( message is FriendRequestMessage )
&& ! ( message is SessionRequestMessage ) && ! isSessionResetMessage
if ! shouldSendAutoGeneratedFR {
let messageSendCopy = messageSend . copy ( with : destination )
messageSender . sendMessage ( messageSendCopy )
} else {
var frMessageSend : OWSMessageSend !
storage . dbReadWriteConnection . readWrite { transaction in // TODO: Y e t a n o t h e r t r a n s a c t i o n
frMessageSend = getAutoGeneratedMultiDeviceFRMessageSend ( for : masterDestination . hexEncodedPublicKey , in : transaction )
DispatchQueue . main . async {
storage . dbReadWriteConnection . readWrite { transaction in
getAutoGeneratedMultiDeviceFRMessageSend ( for : destination . hexEncodedPublicKey , in : transaction )
. done ( on : OWSDispatch . sendingQueue ( ) ) { autoGeneratedFRMessageSend in
messageSender . sendMessage ( autoGeneratedFRMessageSend )
}
messageSender . sendMessage ( frMessageSend )
}
}
// S e n d t o s l a v e d e s t i n a t i o n s ( u s i n g a b e s t a t t e m p t a p p r o a c h ( i . e . i g n o r i n g t h e m e s s a g e s e n d r e s u l t ) f o r n o w )
let slaveDestinations = destinations . filter { $0 . kind = = . slave }
for slaveDestination in slaveDestinations {
let thread = TSContactThread . getOrCreateThread ( contactId : slaveDestination . hexEncodedPublicKey ) // TODO: I g u e s s i t ' s o k a y t h i s s t a r t s a n e w t r a n s a c t i o n ?
if thread . isContactFriend || isSilentMessage || isFriendRequestMessage || isSessionRequestMessage || isGroupMessage {
let messageSendCopy = messageSend . copy ( with : slaveDestination )
messageSender . sendMessage ( messageSendCopy )
}
}
promise . catch ( on : OWSDispatch . sendingQueue ( ) ) { error in
print ( " [Loki] Couldn't get thread for destination: \( destination . hexEncodedPublicKey ) . " )
}
if let thread = TSContactThread . getWithContactId ( destination . hexEncodedPublicKey , transaction : transaction ) {
seal . fulfill ( thread )
} else {
var frMessageSend : OWSMessageSend !
// FIXME: T h i s c r a s h e s s o m e t i m e s d u e t o t r a n s a c t i o n n e s t i n g
storage . dbReadWriteConnection . readWrite { transaction in // TODO: Y e t a n o t h e r t r a n s a c t i o n
frMessageSend = getAutoGeneratedMultiDeviceFRMessageSend ( for : slaveDestination . hexEncodedPublicKey , in : transaction )
DispatchQueue . main . async {
storage . dbReadWriteConnection . readWrite { transaction in
let thread = TSContactThread . getOrCreateThread ( withContactId : destination . hexEncodedPublicKey , transaction : transaction )
seal . fulfill ( thread )
}
}
messageSender . sendMessage ( frMessageSend )
}
}
@objc ( sendMessageToDestinationAndLinkedDevices : in : )
public static func sendMessageToDestinationAndLinkedDevices ( _ messageSend : OWSMessageSend , in transaction : YapDatabaseReadTransaction ) {
let message = messageSend . message
let messageSender = SSKEnvironment . shared . messageSender
if ! isMultiDeviceRequired ( for : message ) {
print ( " [Loki] sendMessageToDestinationAndLinkedDevices(_:in:) invoked for a message that doesn't require multi device routing. " )
OWSDispatch . sendingQueue ( ) . async {
messageSender . sendMessage ( messageSend )
}
return
}
print ( " [Loki] Sending \( type ( of : message ) ) message using multi device routing. " )
let recipientID = messageSend . recipient . recipientId ( )
getMultiDeviceDestinations ( for : recipientID , in : transaction ) . done ( on : OWSDispatch . sendingQueue ( ) ) { destinations in
let masterDestination = destinations . first { $0 . isMaster }
if let masterDestination = masterDestination {
storage . dbReadConnection . read { transaction in
sendMessage ( messageSend , to : masterDestination , in : transaction )
}
}
let slaveDestinations = destinations . filter { ! $0 . isMaster }
slaveDestinations . forEach { slaveDestination in
storage . dbReadConnection . read { transaction in
sendMessage ( messageSend , to : slaveDestination , in : transaction )
}
}
} . catch ( on : OWSDispatch . sendingQueue ( ) ) { error in
// P r o c e e d e v e n i f u p d a t i n g t h e l i n k e d d e v i c e s m a p f a i l e d s o t h a t m e s s a g e s e n d i n g
// i s i n d e p e n d e n t o f w h e t h e r t h e f i l e s e r v e r i s u p
// P r o c e e d e v e n i f u p d a t i n g t h e re c i p i e n t ' s d e v i c e l i n k s f a i l e d , s o t h a t m e s s a g e s e n d i n g
// i s i n d e p e n d e n t o f w h e t h e r t h e f i l e s e r v e r i s on l i n e
messageSender . sendMessage ( messageSend )
} . retainUntilComplete ( )
}
}
@objc ( updateDeviceLinksIfNeededForHexEncodedPublicKey : in : )
public static func updateDeviceLinksIfNeeded ( for hexEncodedPublicKey : String , in transaction : YapDatabaseReadWriteTransaction ) -> AnyPromise {
public static func updateDeviceLinksIfNeeded ( for hexEncodedPublicKey : String , in transaction : YapDatabaseRead Transaction) -> AnyPromise {
let promise = getMultiDeviceDestinations ( for : hexEncodedPublicKey , in : transaction )
return AnyPromise . from ( promise )
}
@ -109,9 +123,6 @@ public final class MultiDeviceProtocol : NSObject {
let masterHexEncodedPublicKey = storage . getMasterHexEncodedPublicKey ( for : hexEncodedPublicKey , in : transaction )
let isSlaveDeviceThread = masterHexEncodedPublicKey != hexEncodedPublicKey
thread . isForceHidden = isSlaveDeviceThread // TODO: C o u l d w e m a k e t h i s c o m p u t e d ?
if thread . friendRequestStatus = = . none || thread . friendRequestStatus = = . requestExpired {
thread . saveFriendRequestStatus ( . requestSent , with : transaction ) // TODO: S h o u l d w e a l w a y s i m m e d i a t e l y m a r k t h e s l a v e d e v i c e a s a f r i e n d ?
}
thread . save ( with : transaction )
let result = FriendRequestMessage ( outgoingMessageWithTimestamp : NSDate . ows_millisecondTimeStamp ( ) , in : thread ,
messageBody : " Please accept to enable messages to be synced across devices " ,
@ -122,22 +133,39 @@ public final class MultiDeviceProtocol : NSObject {
}
@objc ( getAutoGeneratedMultiDeviceFRMessageSendForHexEncodedPublicKey : in : )
public static func getAutoGeneratedMultiDeviceFRMessageSend ( for hexEncodedPublicKey : String , in transaction : YapDatabaseReadWriteTransaction ) -> OWSMessageSend {
public static func objc_getAutoGeneratedMultiDeviceFRMessageSend ( for hexEncodedPublicKey : String , in transaction : YapDatabaseReadWriteTransaction ) -> AnyPromise {
return AnyPromise . from ( getAutoGeneratedMultiDeviceFRMessageSend ( for : hexEncodedPublicKey , in : transaction ) )
}
public static func getAutoGeneratedMultiDeviceFRMessageSend ( for hexEncodedPublicKey : String , in transaction : YapDatabaseReadWriteTransaction ) -> Promise < OWSMessageSend > {
let thread = TSContactThread . getOrCreateThread ( withContactId : hexEncodedPublicKey , transaction : transaction )
let message = getAutoGeneratedMultiDeviceFRMessage ( for : hexEncodedPublicKey , in : transaction )
thread . friendRequestStatus = . requestSending
thread . save ( with : transaction )
let recipient = SignalRecipient . getOrBuildUnsavedRecipient ( forRecipientId : hexEncodedPublicKey , transaction : transaction )
let udManager = SSKEnvironment . shared . udManager
let senderCertificate = udManager . getSenderCertificate ( )
let ( promise , seal ) = Promise < OWSMessageSend > . pending ( )
DispatchQueue . main . async {
var recipientUDAccess : OWSUDAccess ?
if let senderCertificate = senderCertificate {
recipientUDAccess = udManager . udAccess ( forRecipientId : hexEncodedPublicKey , requireSyncAccess : true )
}
return OWSMessageSend ( message : message , thread : thread , recipient : recipient , senderCertificate : senderCertificate ,
let messageSend = OWSMessageSend ( message : message , thread : thread , recipient : recipient , senderCertificate : senderCertificate ,
udAccess : recipientUDAccess , localNumber : getUserHexEncodedPublicKey ( ) , success : {
DispatchQueue . main . async {
thread . friendRequestStatus = . requestSent
thread . save ( )
}
} , failure : { _ in
DispatchQueue . main . async {
thread . friendRequestStatus = . none
thread . save ( )
}
} )
seal . fulfill ( messageSend )
}
return promise
}
// MARK: - R e c e i v i n g
@ -190,7 +218,7 @@ public final class MultiDeviceProtocol : NSObject {
if ! deviceLinks . contains ( where : { $0 . master . hexEncodedPublicKey = = hexEncodedPublicKey && $0 . slave . hexEncodedPublicKey = = getUserHexEncodedPublicKey ( ) } ) {
return
}
LokiFileServerAPI . getDeviceLinks ( associatedWith : getUserHexEncodedPublicKey ( ) , in : transaction ). done ( on : . main ) { deviceLinks in
LokiFileServerAPI . getDeviceLinks ( associatedWith : getUserHexEncodedPublicKey ( ) ). done ( on : DispatchQueue . main ) { deviceLinks in
if deviceLinks . contains ( where : { $0 . master . hexEncodedPublicKey = = hexEncodedPublicKey && $0 . slave . hexEncodedPublicKey = = getUserHexEncodedPublicKey ( ) } ) {
UserDefaults . standard [ . wasUnlinked ] = true
NotificationCenter . default . post ( name : . dataNukeRequested , object : nil )
@ -203,17 +231,16 @@ public final class MultiDeviceProtocol : NSObject {
// H e r e ( i n a n o n - @ o b j c e x t e n s i o n ) b e c a u s e i t d o e s n ' t i n t e r o p e r a t e w e l l w i t h O b j - C
public extension MultiDeviceProtocol {
fileprivate static func getMultiDeviceDestinations ( for hexEncodedPublicKey : String , in transaction : YapDatabaseReadWriteTransaction ) -> Promise < Set < MultiDeviceDestination > > {
// FIXME: T h r e a d i n g
fileprivate static func getMultiDeviceDestinations ( for hexEncodedPublicKey : String , in transaction : YapDatabaseReadTransaction ) -> Promise < Set < MultiDeviceDestination > > {
let ( promise , seal ) = Promise < Set < MultiDeviceDestination > > . pending ( )
func getDestinations ( in transaction : YapDatabaseReadTransaction ? = nil ) {
storage . dbReadConnection . read { transaction in
var destinations : Set < MultiDeviceDestination > = [ ]
let masterHexEncodedPublicKey = storage . getMasterHexEncodedPublicKey ( for : hexEncodedPublicKey , in : transaction ) ? ? hexEncodedPublicKey
let masterDestination = MultiDeviceDestination ( hexEncodedPublicKey : masterHexEncodedPublicKey , kind: . master )
let masterDestination = MultiDeviceDestination ( hexEncodedPublicKey : masterHexEncodedPublicKey , isMaster: true )
destinations . insert ( masterDestination )
let deviceLinks = storage . getDeviceLinks ( for : masterHexEncodedPublicKey , in : transaction )
let slaveDestinations = deviceLinks . map { MultiDeviceDestination ( hexEncodedPublicKey : $0 . slave . hexEncodedPublicKey , kind: . slav e) }
let slaveDestinations = deviceLinks . map { MultiDeviceDestination ( hexEncodedPublicKey : $0 . slave . hexEncodedPublicKey , isMaster: fals e) }
destinations . formUnion ( slaveDestinations )
seal . fulfill ( destinations )
}
@ -226,7 +253,7 @@ public extension MultiDeviceProtocol {
}
if timeSinceLastUpdate > deviceLinkUpdateInterval {
let masterHexEncodedPublicKey = storage . getMasterHexEncodedPublicKey ( for : hexEncodedPublicKey , in : transaction ) ? ? hexEncodedPublicKey
LokiFileServerAPI . getDeviceLinks ( associatedWith : masterHexEncodedPublicKey , in : transaction ). done ( on : LokiAPI . workQueue ) { _ in
LokiFileServerAPI . getDeviceLinks ( associatedWith : masterHexEncodedPublicKey ). done ( on : LokiAPI . workQueue ) { _ in
getDestinations ( )
lastDeviceLinkUpdate [ hexEncodedPublicKey ] = Date ( )
} . catch ( on : LokiAPI . workQueue ) { error in