@ -19,7 +19,7 @@ public final class MultiDeviceProtocol : NSObject {
set { LokiAPI . stateQueue . sync { _lastDeviceLinkUpdate = newValue } }
}
// TODO: I d o n ' t t h i n k th i s st a t e Q u e u e s t u f f a c t u a l l y h e l p s a v o i d r a c e c o n d i t i o n s
// TODO: I d o n ' t t h i n k st a t e Q u e u e a c t u a l l y h e l p s a v o i d r a c e c o n d i t i o n s
internal static var storage : OWSPrimaryStorage { OWSPrimaryStorage . shared ( ) }
@ -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,110 @@ 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 ( )
}
private static func copy ( _ messageSend : OWSMessageSend , for destination : MultiDeviceDestination , with seal : Resolver < Void > ) -> OWSMessageSend {
var recipient : SignalRecipient !
storage . dbReadConnection . read { transaction in
recipient = SignalRecipient . getOrBuildUnsavedRecipient ( forRecipientId : destination . hexEncodedPublicKey , transaction : transaction )
}
// TODO: W h y i s i t o k a y t h a t t h e t h r e a d , s e n d e r c e r t i f i c a t e , e t c . d o n ' t g e t c h a n g e d ?
return OWSMessageSend ( message : messageSend . message , thread : messageSend . thread , recipient : recipient ,
senderCertificate : messageSend . senderCertificate , udAccess : messageSend . udAccess , localNumber : messageSend . localNumber , success : {
seal . fulfill ( ( ) )
} , failure : { error in
seal . reject ( error )
} )
}
private static func sendMessage ( _ messageSend : OWSMessageSend , to destination : MultiDeviceDestination , in transaction : YapDatabaseReadTransaction ) -> Promise < Void > {
let ( threadPromise , threadPromiseSeal ) = Promise < TSContactThread > . pending ( )
if let thread = TSContactThread . getWithContactId ( destination . hexEncodedPublicKey , transaction : transaction ) {
threadPromiseSeal . fulfill ( thread )
} else {
// D i s p a t c h a s y n c o n t h e m a i n q u e u e t o a v o i d n e s t e d w r i t e t r a n s a c t i o n s
DispatchQueue . main . async {
storage . dbReadWriteConnection . readWrite { transaction in
let thread = TSContactThread . getOrCreateThread ( withContactId : destination . hexEncodedPublicKey , transaction : transaction )
threadPromiseSeal . fulfill ( thread )
}
}
}
return threadPromise . then ( on : OWSDispatch . sendingQueue ( ) ) { thread -> Promise < Void > in
let message = messageSend . message
let messageSender = SSKEnvironment . shared . messageSender
let ( promise , seal ) = Promise < Void > . pending ( )
let shouldSendAutoGeneratedFR = ! thread . isContactFriend && ! ( message is FriendRequestMessage )
&& message . shouldBeSaved ( ) // s h o u l d B e S a v e d i n d i c a t e s i t i s n ' t a t r a n s i e n t m e s s a g e
if ! shouldSendAutoGeneratedFR {
let messageSendCopy = copy ( messageSend , for : destination , with : seal )
messageSender . sendMessage ( messageSendCopy )
} else {
// D i s p a t c h a s y n c o n t h e m a i n q u e u e t o a v o i d n e s t e d w r i t e t r a n s a c t i o n s
DispatchQueue . main . async {
storage . dbReadWriteConnection . readWrite { transaction in
getAutoGeneratedMultiDeviceFRMessageSend ( for : destination . hexEncodedPublicKey , in : transaction , seal : seal )
. done ( on : OWSDispatch . sendingQueue ( ) ) { autoGeneratedFRMessageSend in
messageSender . sendMessage ( autoGeneratedFRMessageSend )
}
}
}
}
return promise
}
}
@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
public static func sendMessageToDestinationAndLinkedDevices ( _ messageSend : OWSMessageSend , in transaction : YapDatabaseReadTransaction ) {
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 )
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
}
let isSilentMessage = message . isSilent || message is EphemeralMessage || message is OWSOutgoingSyncMessage
let isFriendRequestMessage = message is FriendRequestMessage
let isSessionRequestMessage = message is SessionRequestMessage
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
// 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 )
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 )
}
messageSender . sendMessage ( frMessageSend )
var promises : [ Promise < Void > ] = [ ]
let masterDestination = destinations . first { $0 . isMaster }
if let masterDestination = masterDestination {
storage . dbReadConnection . read { transaction in
promises . append ( sendMessage ( messageSend , to : masterDestination , in : transaction ) )
}
}
// 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 )
} 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 )
let slaveDestinations = destinations . filter { ! $0 . isMaster }
slaveDestinations . forEach { slaveDestination in
storage . dbReadConnection . read { transaction in
promises . append ( sendMessage ( messageSend , to : slaveDestination , in : transaction ) )
}
}
when ( resolved : promises ) . done ( on : OWSDispatch . sendingQueue ( ) ) { results in
let errors = results . compactMap { result -> Error ? in
if case Result . rejected ( let error ) = result {
return error
} else {
return nil
}
messageSender . sendMessage ( frMessageSend )
}
if errors . isEmpty {
messageSend . success ( )
} else {
messageSend . failure ( errors . first ! )
}
}
} . 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 up
// 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 : YapDatabaseRead Write Transaction) -> AnyPromise {
public static func updateDeviceLinksIfNeeded ( for hexEncodedPublicKey : String , in transaction : YapDatabaseRead Transaction) -> AnyPromise {
let promise = getMultiDeviceDestinations ( for : hexEncodedPublicKey , in : transaction )
return AnyPromise . from ( promise )
}
@ -120,21 +163,33 @@ 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 , seal externalSeal : Resolver < Void > ? = nil ) -> Promise < OWSMessageSend > {
// W e d o n ' t u p d a t e f r i e n d r e q u e s t s t a t u s h e r e a s i t i s d o n e i n O W S M e s s a g e S e n d e r . s e n d M e s s a g e ( : )
let thread = TSContactThread . getOrCreateThread ( withContactId : hexEncodedPublicKey , transaction : transaction )
let message = getAutoGeneratedMultiDeviceFRMessage ( for : hexEncodedPublicKey , in : transaction )
let recipient = SignalRecipient . getOrBuildUnsavedRecipient ( forRecipientId : hexEncodedPublicKey , transaction : transaction )
let udManager = SSKEnvironment . shared . udManager
let senderCertificate = udManager . getSenderCertificate ( )
var recipientUDAccess : OWSUDAccess ?
if let senderCertificate = senderCertificate {
recipientUDAccess = udManager . udAccess ( forRecipientId : hexEncodedPublicKey , requireSyncAccess : true )
let ( promise , seal ) = Promise < OWSMessageSend > . pending ( )
// D i s p a t c h a s y n c o n t h e m a i n q u e u e t o a v o i d n e s t e d w r i t e t r a n s a c t i o n s
DispatchQueue . main . async {
var recipientUDAccess : OWSUDAccess ?
if let senderCertificate = senderCertificate {
recipientUDAccess = udManager . udAccess ( forRecipientId : hexEncodedPublicKey , requireSyncAccess : true ) // S t a r t s a n e w w r i t e t r a n s a c t i o n i n t e r n a l l y
}
let messageSend = OWSMessageSend ( message : message , thread : thread , recipient : recipient , senderCertificate : senderCertificate ,
udAccess : recipientUDAccess , localNumber : getUserHexEncodedPublicKey ( ) , success : {
externalSeal ? . fulfill ( ( ) )
} , failure : { error in
externalSeal ? . reject ( error )
} )
seal . fulfill ( messageSend )
}
return OWSMessageSend ( message : message , thread : thread , recipient : recipient , senderCertificate : senderCertificate ,
udAccess : recipientUDAccess , localNumber : getUserHexEncodedPublicKey ( ) , success : {
} , failure : { _ in
} )
return promise
}
// MARK: - R e c e i v i n g
@ -157,8 +212,8 @@ public final class MultiDeviceProtocol : NSObject {
}
// S e t a n y p r o f i l e i n f o ( t h e d e v i c e l i n k a u t h o r i z a t i o n a l s o i n c l u d e s t h e m a s t e r d e v i c e ' s p r o f i l e i n f o )
if let dataMessage = protoContent . dataMessage {
Session Protocol. updateDisplayNameIfNeeded ( for : master , using : dataMessage , appendingShortID : false , in : transaction )
Session Protocol. updateProfileKeyIfNeeded ( for : master , using : dataMessage )
Session Meta Protocol. updateDisplayNameIfNeeded ( for : master , using : dataMessage , appendingShortID : false , in : transaction )
Session Meta Protocol. updateProfileKeyIfNeeded ( for : master , using : dataMessage )
}
} else { // R e q u e s t
print ( " [Loki] Received a device link request from: \( hexEncodedPublicKey ) . " ) // I n t e n t i o n a l l y n o t ` s l a v e `
@ -187,7 +242,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 )
@ -200,17 +255,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 )
}
@ -223,7 +277,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