2019-04-30 06:27:39 +02:00
import PromiseKit
2019-06-12 06:50:36 +02:00
@objc ( LKAPI )
public final class LokiAPI : NSObject {
2020-03-27 01:52:42 +01:00
private static let stateQueue = DispatchQueue ( label : " stateQueue " )
2020-02-19 00:00:49 +01:00
// / O n l y e v e r m o d i f i e d f r o m t h e m e s s a g e p r o c e s s i n g q u e u e ( ` O W S B a t c h M e s s a g e P r o c e s s o r . p r o c e s s i n g Q u e u e ` ) .
2019-11-27 04:54:45 +01:00
private static var syncMessageTimestamps : [ String : Set < UInt64 > ] = [ : ]
2020-02-19 00:00:49 +01:00
2020-03-27 01:52:42 +01:00
private static var _lastDeviceLinkUpdate : [ String : Date ] = [ : ]
2020-02-19 00:00:49 +01:00
// / A m a p p i n g f r o m h e x e n c o d e d p u b l i c k e y t o d a t e u p d a t e d .
public static var lastDeviceLinkUpdate : [ String : Date ] {
get { stateQueue . sync { _lastDeviceLinkUpdate } }
set { stateQueue . sync { _lastDeviceLinkUpdate = newValue } }
}
private static var _userHexEncodedPublicKeyCache : [ String : Set < String > ] = [ : ]
// / A m a p p i n g f r o m t h r e a d I D t o s e t o f u s e r h e x e n c o d e d p u b l i c k e y s .
@objc public static var userHexEncodedPublicKeyCache : [ String : Set < String > ] {
get { stateQueue . sync { _userHexEncodedPublicKeyCache } }
set { stateQueue . sync { _userHexEncodedPublicKeyCache = newValue } }
}
// / A l l s e r v i c e n o d e r e l a t e d e r r o r s m u s t b e h a n d l e d o n t h i s q u e u e t o a v o i d r a c e c o n d i t i o n s m a i n t a i n i n g e . g . f a i l u r e c o u n t s .
2020-01-20 05:04:22 +01:00
public static let errorHandlingQueue = DispatchQueue ( label : " errorHandlingQueue " )
2019-04-30 06:27:39 +02:00
2019-09-17 01:56:47 +02:00
// MARK: C o n v e n i e n c e
2019-09-26 03:32:47 +02:00
internal static let storage = OWSPrimaryStorage . shared ( )
2020-01-30 10:09:02 +01:00
internal static let userHexEncodedPublicKey = getUserHexEncodedPublicKey ( )
2019-06-12 06:44:28 +02:00
2019-05-21 05:44:46 +02:00
// MARK: S e t t i n g s
2020-02-19 00:00:49 +01:00
private static let apiVersion = " v1 "
2019-09-18 08:59:11 +02:00
private static let maxRetryCount : UInt = 8
2019-06-18 08:01:53 +02:00
private static let defaultTimeout : TimeInterval = 20
2019-06-12 06:23:01 +02:00
private static let longPollingTimeout : TimeInterval = 40
2019-10-09 01:37:44 +02:00
private static var userIDScanLimit : UInt = 4096
2019-10-04 06:52:59 +02:00
internal static var powDifficulty : UInt = 4
2019-10-09 01:37:44 +02:00
public static let defaultMessageTTL : UInt64 = 24 * 60 * 60 * 1000
2020-02-13 06:40:20 +01:00
public static let deviceLinkUpdateInterval : TimeInterval = 20
2019-05-23 03:49:05 +02:00
2019-04-30 06:27:39 +02:00
// MARK: T y p e s
2019-05-07 05:53:31 +02:00
public typealias RawResponse = Any
2019-04-30 06:27:39 +02:00
2020-02-14 00:16:53 +01:00
@objc public class LokiAPIError : NSError { // N o t c a l l e d ` E r r o r ` f o r O b j - C i n t e r o p e r a b l i t y
2019-05-23 05:36:50 +02:00
2020-02-14 00:16:53 +01:00
@objc public static let proofOfWorkCalculationFailed = LokiAPIError ( domain : " LokiAPIErrorDomain " , code : 1 , userInfo : [ NSLocalizedDescriptionKey : " Failed to calculate proof of work. " ] )
@objc public static let messageConversionFailed = LokiAPIError ( domain : " LokiAPIErrorDomain " , code : 2 , userInfo : [ NSLocalizedDescriptionKey : " Failed to construct message. " ] )
@objc public static let clockOutOfSync = LokiAPIError ( domain : " LokiAPIErrorDomain " , code : 3 , userInfo : [ NSLocalizedDescriptionKey : " Your clock is out of sync with the service node network. " ] )
@objc public static let randomSnodePoolUpdatingFailed = LokiAPIError ( domain : " LokiAPIErrorDomain " , code : 4 , userInfo : [ NSLocalizedDescriptionKey : " Failed to update random service node pool. " ] )
2020-03-25 05:52:25 +01:00
@objc public static let missingSnodeVersion = LokiAPIError ( domain : " LokiAPIErrorDomain " , code : 5 , userInfo : [ NSLocalizedDescriptionKey : " Missing service node version. " ] )
2019-05-07 02:10:15 +02:00
}
2019-09-30 04:08:55 +02:00
@objc ( LKDestination )
public final class Destination : NSObject {
@objc public let hexEncodedPublicKey : String
@objc ( kind )
public let objc_kind : String
2019-09-24 02:07:18 +02:00
2019-10-04 03:43:28 +02:00
public var kind : Kind {
return Kind ( rawValue : objc_kind ) !
}
2019-09-30 04:08:55 +02:00
public enum Kind : String { case master , slave }
public init ( hexEncodedPublicKey : String , kind : Kind ) {
self . hexEncodedPublicKey = hexEncodedPublicKey
self . objc_kind = kind . rawValue
}
@objc public init ( hexEncodedPublicKey : String , kind : String ) {
self . hexEncodedPublicKey = hexEncodedPublicKey
self . objc_kind = kind
}
2019-10-03 08:46:08 +02:00
override public func isEqual ( _ other : Any ? ) -> Bool {
guard let other = other as ? Destination else { return false }
return hexEncodedPublicKey = = other . hexEncodedPublicKey && kind = = other . kind
}
override public var hash : Int { // O v e r r i d e N S O b j e c t . h a s h a n d n o t H a s h a b l e . h a s h V a l u e o r H a s h a b l e . h a s h ( i n t o : )
return hexEncodedPublicKey . hashValue ^ kind . hashValue
}
override public var description : String { return " \( kind . rawValue ) ( \( hexEncodedPublicKey ) ) " }
2019-09-24 02:07:18 +02:00
}
2019-05-28 01:57:54 +02:00
public typealias MessageListPromise = Promise < [ SSKProtoEnvelope ] >
2020-02-19 00:00:49 +01:00
2019-05-27 04:54:59 +02:00
public typealias RawResponsePromise = Promise < RawResponse >
2019-05-27 02:27:49 +02:00
2019-04-30 06:27:39 +02:00
// MARK: L i f e c y c l e
2019-05-06 08:13:32 +02:00
override private init ( ) { }
2019-04-30 06:27:39 +02:00
2019-05-21 05:26:51 +02:00
// MARK: I n t e r n a l A P I
2019-06-12 06:23:01 +02:00
internal static func invoke ( _ method : LokiAPITarget . Method , on target : LokiAPITarget , associatedWith hexEncodedPublicKey : String ,
parameters : [ String : Any ] , headers : [ String : String ] ? = nil , timeout : TimeInterval ? = nil ) -> RawResponsePromise {
2020-02-19 00:00:49 +01:00
let url = URL ( string : " \( target . address ) : \( target . port ) /storage_rpc/ \( apiVersion ) " ) !
2019-06-12 06:23:01 +02:00
let request = TSRequest ( url : url , method : " POST " , parameters : [ " method " : method . rawValue , " params " : parameters ] )
if let headers = headers { request . allHTTPHeaderFields = headers }
2019-06-18 03:27:19 +02:00
request . timeoutInterval = timeout ? ? defaultTimeout
2019-06-13 06:34:19 +02:00
let headers = request . allHTTPHeaderFields ? ? [ : ]
2019-06-14 02:21:32 +02:00
let headersDescription = headers . isEmpty ? " no custom headers specified " : headers . prettifiedDescription
print ( " [Loki] Invoking \( method . rawValue ) on \( target ) with \( parameters . prettifiedDescription ) ( \( headersDescription ) ). " )
2020-01-24 05:55:07 +01:00
return LokiSnodeProxy ( for : target ) . perform ( request , withCompletionQueue : DispatchQueue . global ( ) )
2020-01-20 03:12:26 +01:00
. handlingSwarmSpecificErrorsIfNeeded ( for : target , associatedWith : hexEncodedPublicKey )
. recoveringNetworkErrorsIfNeeded ( )
2019-06-12 04:36:34 +02:00
}
2019-06-12 06:44:28 +02:00
internal static func getRawMessages ( from target : LokiAPITarget , usingLongPolling useLongPolling : Bool ) -> RawResponsePromise {
2019-06-11 08:22:35 +02:00
let lastHashValue = getLastMessageHashValue ( for : target ) ? ? " "
2019-07-25 05:09:22 +02:00
let parameters = [ " pubKey " : userHexEncodedPublicKey , " lastHash " : lastHashValue ]
2019-06-12 06:23:01 +02:00
let headers : [ String : String ] ? = useLongPolling ? [ " X-Loki-Long-Poll " : " true " ] : nil
let timeout : TimeInterval ? = useLongPolling ? longPollingTimeout : nil
2019-07-25 05:09:22 +02:00
return invoke ( . getMessages , on : target , associatedWith : userHexEncodedPublicKey , parameters : parameters , headers : headers , timeout : timeout )
2019-06-12 04:00:40 +02:00
}
2019-09-30 04:08:55 +02:00
// MARK: P u b l i c A P I
public static func getMessages ( ) -> Promise < Set < MessageListPromise > > {
return getTargetSnodes ( for : userHexEncodedPublicKey ) . mapValues { targetSnode in
return getRawMessages ( from : targetSnode , usingLongPolling : false ) . map { parseRawMessagesResponse ( $0 , from : targetSnode ) }
} . map { Set ( $0 ) } . retryingIfNeeded ( maxRetryCount : maxRetryCount )
}
2020-03-10 06:14:57 +01:00
public static func getDestinations ( for hexEncodedPublicKey : String ) -> Promise < [ Destination ] > {
var result : Promise < [ Destination ] >!
2020-03-06 05:56:05 +01:00
storage . dbReadWriteConnection . readWrite { transaction in
2020-02-14 06:32:47 +01:00
result = getDestinations ( for : hexEncodedPublicKey , in : transaction )
}
return result
}
2020-03-10 06:14:57 +01:00
public static func getDestinations ( for hexEncodedPublicKey : String , in transaction : YapDatabaseReadWriteTransaction ) -> Promise < [ Destination ] > {
2020-02-19 03:55:58 +01:00
// A l l o f t h i s h a s t o h a p p e n o n D i s p a t c h Q u e u e . g l o b a l ( ) d u e t o t h e w a y O W S M e s s a g e M a n a g e r w o r k s
2020-03-10 06:14:57 +01:00
let ( promise , seal ) = Promise < [ Destination ] > . pending ( )
2020-02-14 06:32:47 +01:00
func getDestinations ( in transaction : YapDatabaseReadTransaction ? = nil ) {
func getDestinationsInternal ( in transaction : YapDatabaseReadTransaction ) {
2020-03-10 06:14:57 +01:00
var destinations : [ Destination ] = [ ]
let masterHexEncodedPublicKey = storage . getMasterHexEncodedPublicKey ( for : hexEncodedPublicKey , in : transaction ) ? ? hexEncodedPublicKey
let masterDestination = Destination ( hexEncodedPublicKey : masterHexEncodedPublicKey , kind : . master )
destinations . append ( masterDestination )
let deviceLinks = storage . getDeviceLinks ( for : masterHexEncodedPublicKey , in : transaction )
let slaveDestinations = deviceLinks . map { Destination ( hexEncodedPublicKey : $0 . slave . hexEncodedPublicKey , kind : . slave ) }
destinations . append ( contentsOf : slaveDestinations )
2019-09-30 04:08:55 +02:00
seal . fulfill ( destinations )
}
2020-02-26 03:58:36 +01:00
if let transaction = transaction , transaction . connection . pendingTransactionCount != 0 {
2020-02-14 06:32:47 +01:00
getDestinationsInternal ( in : transaction )
} else {
storage . dbReadConnection . read { transaction in
getDestinationsInternal ( in : transaction )
}
}
2019-09-30 04:08:55 +02:00
}
let timeSinceLastUpdate : TimeInterval
if let lastDeviceLinkUpdate = lastDeviceLinkUpdate [ hexEncodedPublicKey ] {
timeSinceLastUpdate = Date ( ) . timeIntervalSince ( lastDeviceLinkUpdate )
} else {
timeSinceLastUpdate = . infinity
}
if timeSinceLastUpdate > deviceLinkUpdateInterval {
2020-02-14 06:32:47 +01:00
let masterHexEncodedPublicKey = storage . getMasterHexEncodedPublicKey ( for : hexEncodedPublicKey , in : transaction ) ? ? hexEncodedPublicKey
LokiFileServerAPI . getDeviceLinks ( associatedWith : masterHexEncodedPublicKey , in : transaction ) . done ( on : DispatchQueue . global ( ) ) { _ in
getDestinations ( )
lastDeviceLinkUpdate [ hexEncodedPublicKey ] = Date ( )
} . catch ( on : DispatchQueue . global ( ) ) { error in
if ( error as ? LokiDotNetAPI . LokiDotNetAPIError ) = = LokiDotNetAPI . LokiDotNetAPIError . parsingFailed {
// D o n ' t i m m e d i a t e l y r e - f e t c h i n c a s e o f f a i l u r e d u e t o a p a r s i n g e r r o r
2019-10-08 01:48:12 +02:00
lastDeviceLinkUpdate [ hexEncodedPublicKey ] = Date ( )
2020-02-14 06:32:47 +01:00
getDestinations ( )
} else {
2020-03-11 04:35:55 +01:00
print ( " [Loki] Failed to get device links due to error: \( error ) . " )
2020-02-14 06:32:47 +01:00
seal . reject ( error )
2019-10-08 01:48:12 +02:00
}
2019-09-30 04:08:55 +02:00
}
} else {
2020-02-14 06:32:47 +01:00
getDestinations ( in : transaction )
2019-09-30 04:08:55 +02:00
}
return promise
}
public static func sendSignalMessage ( _ signalMessage : SignalMessage , onP2PSuccess : @ escaping ( ) -> Void ) -> Promise < Set < RawResponsePromise > > {
2020-02-14 00:16:53 +01:00
guard let lokiMessage = LokiMessage . from ( signalMessage : signalMessage ) else { return Promise ( error : LokiAPIError . messageConversionFailed ) }
2019-12-11 06:08:08 +01:00
let notificationCenter = NotificationCenter . default
2019-05-27 04:26:37 +02:00
let destination = lokiMessage . destination
2019-06-12 06:44:28 +02:00
func sendLokiMessage ( _ lokiMessage : LokiMessage , to target : LokiAPITarget ) -> RawResponsePromise {
2019-05-27 04:26:37 +02:00
let parameters = lokiMessage . toJSON ( )
2019-05-27 04:50:30 +02:00
return invoke ( . sendMessage , on : target , associatedWith : destination , parameters : parameters )
2019-05-23 05:36:50 +02:00
}
2019-05-27 04:54:59 +02:00
func sendLokiMessageUsingSwarmAPI ( ) -> Promise < Set < RawResponsePromise > > {
2019-12-11 06:08:08 +01:00
notificationCenter . post ( name : . calculatingPoW , object : NSNumber ( value : signalMessage . timestamp ) )
2020-02-19 03:55:58 +01:00
return lokiMessage . calculatePoW ( ) . then { lokiMessageWithPoW -> Promise < Set < RawResponsePromise > > in
2020-02-20 03:30:30 +01:00
notificationCenter . post ( name : . routing , object : NSNumber ( value : signalMessage . timestamp ) )
2019-08-02 01:28:04 +02:00
return getTargetSnodes ( for : destination ) . map { swarm in
return Set ( swarm . map { target in
2020-02-20 03:30:30 +01:00
notificationCenter . post ( name : . messageSending , object : NSNumber ( value : signalMessage . timestamp ) )
2019-12-11 06:08:08 +01:00
return sendLokiMessage ( lokiMessageWithPoW , to : target ) . map { rawResponse in
2019-08-02 01:28:04 +02:00
if let json = rawResponse as ? JSON , let powDifficulty = json [ " difficulty " ] as ? Int {
guard powDifficulty != LokiAPI . powDifficulty else { return rawResponse }
print ( " [Loki] Setting proof of work difficulty to \( powDifficulty ) . " )
LokiAPI . powDifficulty = UInt ( powDifficulty )
} else {
print ( " [Loki] Failed to update proof of work difficulty from: \( rawResponse ) . " )
}
return rawResponse
2019-06-14 02:04:07 +02:00
}
2019-08-02 01:28:04 +02:00
} )
} . retryingIfNeeded ( maxRetryCount : maxRetryCount )
}
2019-05-23 05:36:50 +02:00
}
2019-06-12 06:50:36 +02:00
if let peer = LokiP2PAPI . getInfo ( for : destination ) , ( lokiMessage . isPing || peer . isOnline ) {
2020-01-24 03:59:47 +01:00
let target = LokiAPITarget ( address : peer . address , port : peer . port , publicKeySet : nil )
2019-05-27 05:50:22 +02:00
return Promise . value ( [ target ] ) . mapValues { sendLokiMessage ( lokiMessage , to : $0 ) } . map { Set ( $0 ) } . retryingIfNeeded ( maxRetryCount : maxRetryCount ) . get { _ in
2019-06-12 06:50:36 +02:00
LokiP2PAPI . markOnline ( destination )
2019-05-27 07:06:54 +02:00
onP2PSuccess ( )
2020-02-19 03:55:58 +01:00
} . recover { error -> Promise < Set < RawResponsePromise > > in
2019-06-12 06:50:36 +02:00
LokiP2PAPI . markOffline ( destination )
2019-05-27 04:26:37 +02:00
if lokiMessage . isPing {
2019-06-13 06:34:19 +02:00
print ( " [Loki] Failed to ping \( destination ) ; marking contact as offline. " )
2019-05-27 08:30:28 +02:00
if let error = error as ? NSError {
error . isRetryable = false
throw error
2019-05-27 04:26:37 +02:00
} else {
throw error
}
2019-05-24 08:07:00 +02:00
}
2019-05-27 07:06:54 +02:00
return sendLokiMessageUsingSwarmAPI ( )
2019-05-24 08:07:00 +02:00
}
2019-05-27 04:26:37 +02:00
} else {
2019-05-27 07:06:54 +02:00
return sendLokiMessageUsingSwarmAPI ( )
2019-05-23 05:36:50 +02:00
}
2019-05-22 08:04:51 +02:00
}
2019-09-30 04:08:55 +02:00
// MARK: P u b l i c A P I ( O b j - C )
@objc ( getDestinationsFor : )
public static func objc_getDestinations ( for hexEncodedPublicKey : String ) -> AnyPromise {
let promise = getDestinations ( for : hexEncodedPublicKey )
return AnyPromise . from ( promise )
2019-09-19 02:29:22 +02:00
}
2020-02-14 06:32:47 +01:00
@objc ( getDestinationsFor : inTransaction : )
public static func objc_getDestinations ( for hexEncodedPublicKey : String , in transaction : YapDatabaseReadWriteTransaction ) -> AnyPromise {
let promise = getDestinations ( for : hexEncodedPublicKey , in : transaction )
return AnyPromise . from ( promise )
}
2019-06-12 04:36:27 +02:00
@objc ( sendSignalMessage : onP2PSuccess : )
public static func objc_sendSignalMessage ( _ signalMessage : SignalMessage , onP2PSuccess : @ escaping ( ) -> Void ) -> AnyPromise {
let promise = sendSignalMessage ( signalMessage , onP2PSuccess : onP2PSuccess ) . mapValues { AnyPromise . from ( $0 ) } . map { Set ( $0 ) }
2019-05-27 04:26:37 +02:00
return AnyPromise . from ( promise )
2019-05-08 02:04:19 +02:00
}
2019-05-22 03:32:32 +02:00
// MARK: P a r s i n g
// T h e p a r s i n g u t i l i t i e s b e l o w u s e a b e s t a t t e m p t a p p r o a c h t o p a r s i n g ; t h e y w a r n f o r p a r s i n g f a i l u r e s b u t d o n ' t t h r o w e x c e p t i o n s .
2019-06-12 06:23:01 +02:00
internal static func parseRawMessagesResponse ( _ rawResponse : Any , from target : LokiAPITarget ) -> [ SSKProtoEnvelope ] {
guard let json = rawResponse as ? JSON , let rawMessages = json [ " messages " ] as ? [ JSON ] else { return [ ] }
updateLastMessageHashValueIfPossible ( for : target , from : rawMessages )
let newRawMessages = removeDuplicates ( from : rawMessages )
2019-07-25 05:09:22 +02:00
let newMessages = parseProtoEnvelopes ( from : newRawMessages )
let newMessageCount = newMessages . count
if newMessageCount = = 1 {
print ( " [Loki] Retrieved 1 new message. " )
} else if ( newMessageCount != 0 ) {
print ( " [Loki] Retrieved \( newMessageCount ) new messages. " )
}
return newMessages
2019-06-12 06:23:01 +02:00
}
2019-06-12 03:55:01 +02:00
private static func updateLastMessageHashValueIfPossible ( for target : LokiAPITarget , from rawMessages : [ JSON ] ) {
2019-06-12 06:23:01 +02:00
if let lastMessage = rawMessages . last , let hashValue = lastMessage [ " hash " ] as ? String , let expirationDate = lastMessage [ " expiration " ] as ? Int {
2019-06-12 06:44:28 +02:00
setLastMessageHashValue ( for : target , hashValue : hashValue , expirationDate : UInt64 ( expirationDate ) )
2019-06-12 06:23:01 +02:00
} else if ( ! rawMessages . isEmpty ) {
2019-06-13 06:34:19 +02:00
print ( " [Loki] Failed to update last message hash value from: \( rawMessages ) . " )
2019-05-21 07:21:51 +02:00
}
}
2019-05-22 03:32:32 +02:00
private static func removeDuplicates ( from rawMessages : [ JSON ] ) -> [ JSON ] {
2019-05-22 04:06:02 +02:00
var receivedMessageHashValues = getReceivedMessageHashValues ( ) ? ? [ ]
2019-05-22 03:32:32 +02:00
return rawMessages . filter { rawMessage in
guard let hashValue = rawMessage [ " hash " ] as ? String else {
2019-06-13 06:34:19 +02:00
print ( " [Loki] Missing hash value for message: \( rawMessage ) . " )
2019-05-22 03:32:32 +02:00
return false
}
let isDuplicate = receivedMessageHashValues . contains ( hashValue )
receivedMessageHashValues . insert ( hashValue )
setReceivedMessageHashValues ( to : receivedMessageHashValues )
return ! isDuplicate
}
2019-05-21 07:21:51 +02:00
}
2019-05-22 03:32:32 +02:00
private static func parseProtoEnvelopes ( from rawMessages : [ JSON ] ) -> [ SSKProtoEnvelope ] {
return rawMessages . compactMap { rawMessage in
guard let base64EncodedData = rawMessage [ " data " ] as ? String , let data = Data ( base64Encoded : base64EncodedData ) else {
2019-06-13 06:34:19 +02:00
print ( " [Loki] Failed to decode data for message: \( rawMessage ) . " )
2019-05-21 05:26:51 +02:00
return nil
}
2019-05-22 04:06:30 +02:00
guard let envelope = try ? LokiMessageWrapper . unwrap ( data : data ) else {
2019-06-13 06:34:19 +02:00
print ( " [Loki] Failed to unwrap data for message: \( rawMessage ) . " )
2019-05-21 05:26:51 +02:00
return nil
}
return envelope
2019-05-22 03:32:32 +02:00
}
}
2019-11-27 04:54:45 +01:00
@objc public static func isDuplicateSyncMessage ( _ syncMessage : SSKProtoSyncMessageSent , from hexEncodedPublicKey : String ) -> Bool {
var timestamps : Set < UInt64 > = syncMessageTimestamps [ hexEncodedPublicKey ] ? ? [ ]
2019-11-27 06:26:15 +01:00
let hasTimestamp = syncMessage . timestamp != 0
guard hasTimestamp else { return false }
2019-11-27 04:54:45 +01:00
let result = timestamps . contains ( syncMessage . timestamp )
timestamps . insert ( syncMessage . timestamp )
syncMessageTimestamps [ hexEncodedPublicKey ] = timestamps
return result
}
2019-08-29 07:52:51 +02:00
2019-10-09 01:37:44 +02:00
// MARK: M e s s a g e H a s h C a c h i n g
2019-08-29 07:52:51 +02:00
private static func getLastMessageHashValue ( for target : LokiAPITarget ) -> String ? {
var result : String ? = nil
// U s e s a r e a d / w r i t e c o n n e c t i o n b e c a u s e g e t t i n g t h e l a s t m e s s a g e h a s h v a l u e a l s o r e m o v e s e x p i r e d m e s s a g e s a s n e e d e d
// TODO: T h i s s h o u l d n ' t b e t h e c a s e ; a g e t t e r s h o u l d n ' t h a v e a n u n e x p e c t e d s i d e e f f e c t
storage . dbReadWriteConnection . readWrite { transaction in
result = storage . getLastMessageHash ( forServiceNode : target . address , transaction : transaction )
}
return result
}
private static func setLastMessageHashValue ( for target : LokiAPITarget , hashValue : String , expirationDate : UInt64 ) {
storage . dbReadWriteConnection . readWrite { transaction in
storage . setLastMessageHash ( forServiceNode : target . address , hash : hashValue , expiresAt : expirationDate , transaction : transaction )
}
}
2020-02-19 00:00:49 +01:00
private static let receivedMessageHashValuesKey = " receivedMessageHashValuesKey "
private static let receivedMessageHashValuesCollection = " receivedMessageHashValuesCollection "
2019-08-29 07:52:51 +02:00
private static func getReceivedMessageHashValues ( ) -> Set < String > ? {
var result : Set < String > ? = nil
storage . dbReadConnection . read { transaction in
result = transaction . object ( forKey : receivedMessageHashValuesKey , inCollection : receivedMessageHashValuesCollection ) as ! Set < String > ?
}
return result
}
private static func setReceivedMessageHashValues ( to receivedMessageHashValues : Set < String > ) {
storage . dbReadWriteConnection . readWrite { transaction in
transaction . setObject ( receivedMessageHashValues , forKey : receivedMessageHashValuesKey , inCollection : receivedMessageHashValuesCollection )
}
}
2019-10-08 06:02:03 +02:00
// MARK: U s e r I D C a c h i n g
2019-10-11 06:52:56 +02:00
@objc public static func cache ( _ hexEncodedPublicKey : String , for threadID : String ) {
if let cache = userHexEncodedPublicKeyCache [ threadID ] {
userHexEncodedPublicKeyCache [ threadID ] = cache . union ( [ hexEncodedPublicKey ] )
2019-10-08 06:02:03 +02:00
} else {
2019-10-11 06:52:56 +02:00
userHexEncodedPublicKeyCache [ threadID ] = [ hexEncodedPublicKey ]
2019-10-09 01:37:44 +02:00
}
}
2019-10-11 06:52:56 +02:00
@objc public static func getMentionCandidates ( for query : String , in threadID : String ) -> [ Mention ] {
2019-10-09 05:46:21 +02:00
// P r e p a r e
2019-10-11 06:52:56 +02:00
guard let cache = userHexEncodedPublicKeyCache [ threadID ] else { return [ ] }
var candidates : [ Mention ] = [ ]
2019-10-09 05:46:21 +02:00
// G a t h e r c a n d i d a t e s
2019-10-15 01:29:41 +02:00
var publicChat : LokiPublicChat ?
2019-10-15 00:43:58 +02:00
storage . dbReadConnection . read { transaction in
2019-10-15 01:29:41 +02:00
publicChat = LokiDatabaseUtilities . getPublicChat ( for : threadID , in : transaction )
2019-10-14 05:40:18 +02:00
}
2019-10-09 05:46:21 +02:00
storage . dbReadConnection . read { transaction in
2019-10-11 06:52:56 +02:00
candidates = cache . flatMap { hexEncodedPublicKey in
2019-10-14 05:40:18 +02:00
let uncheckedDisplayName : String ?
2019-10-15 01:29:41 +02:00
if let publicChat = publicChat {
2020-01-30 23:42:36 +01:00
uncheckedDisplayName = UserDisplayNameUtilities . getPublicChatDisplayName ( for : hexEncodedPublicKey , in : publicChat . channel , on : publicChat . server )
2019-10-14 05:40:18 +02:00
} else {
2020-01-30 23:42:36 +01:00
uncheckedDisplayName = UserDisplayNameUtilities . getPrivateChatDisplayName ( for : hexEncodedPublicKey )
2019-10-14 05:40:18 +02:00
}
guard let displayName = uncheckedDisplayName else { return nil }
2019-10-11 06:52:56 +02:00
guard ! displayName . hasPrefix ( " Anonymous " ) else { return nil }
return Mention ( hexEncodedPublicKey : hexEncodedPublicKey , displayName : displayName )
2019-10-09 05:46:21 +02:00
}
}
2019-10-15 02:23:03 +02:00
candidates = candidates . filter { $0 . hexEncodedPublicKey != userHexEncodedPublicKey }
2019-10-09 05:46:21 +02:00
// S o r t a l p h a b e t i c a l l y f i r s t
candidates . sort { $0 . displayName < $1 . displayName }
if query . count >= 2 {
// F i l t e r o u t a n y n o n - m a t c h i n g c a n d i d a t e s
2019-10-10 06:29:44 +02:00
candidates = candidates . filter { $0 . displayName . lowercased ( ) . contains ( query . lowercased ( ) ) }
2019-10-09 05:46:21 +02:00
// S o r t b a s e d o n w h e r e i n t h e c a n d i d a t e t h e q u e r y o c c u r s
2019-10-10 06:29:44 +02:00
candidates . sort {
$0 . displayName . lowercased ( ) . range ( of : query . lowercased ( ) ) ! . lowerBound < $1 . displayName . lowercased ( ) . range ( of : query . lowercased ( ) ) ! . lowerBound
}
2019-10-09 05:46:21 +02:00
}
// R e t u r n
2019-10-11 06:52:56 +02:00
return candidates
2019-10-09 05:46:21 +02:00
}
2019-10-11 06:52:56 +02:00
@objc public static func populateUserHexEncodedPublicKeyCacheIfNeeded ( for threadID : String , in transaction : YapDatabaseReadWriteTransaction ? = nil ) {
guard userHexEncodedPublicKeyCache [ threadID ] = = nil else { return }
2019-10-09 01:37:44 +02:00
var result : Set < String > = [ ]
2019-10-09 02:16:10 +02:00
func populate ( in transaction : YapDatabaseReadWriteTransaction ) {
2019-10-09 01:37:44 +02:00
guard let thread = TSThread . fetch ( uniqueId : threadID , transaction : transaction ) else { return }
let interactions = transaction . ext ( TSMessageDatabaseViewExtensionName ) as ! YapDatabaseViewTransaction
interactions . enumerateKeysAndObjects ( inGroup : threadID ) { _ , _ , object , index , _ in
guard let message = object as ? TSIncomingMessage , index < userIDScanLimit else { return }
result . insert ( message . authorId )
}
2019-10-08 06:02:03 +02:00
}
2019-10-09 02:16:10 +02:00
if let transaction = transaction {
populate ( in : transaction )
} else {
storage . dbReadWriteConnection . readWrite { transaction in
populate ( in : transaction )
}
}
2019-10-09 01:37:44 +02:00
result . insert ( userHexEncodedPublicKey )
2019-10-11 06:52:56 +02:00
userHexEncodedPublicKeyCache [ threadID ] = result
2019-10-08 06:02:03 +02:00
}
2019-08-29 07:52:51 +02:00
}
// MARK: E r r o r H a n d l i n g
private extension Promise {
fileprivate func recoveringNetworkErrorsIfNeeded ( ) -> Promise < T > {
2020-02-19 03:55:58 +01:00
return recover { error -> Promise < T > in
2019-08-29 07:52:51 +02:00
switch error {
case NetworkManagerError . taskError ( _ , let underlyingError ) : throw underlyingError
2020-01-24 05:55:07 +01:00
case LokiHTTPClient . HTTPError . networkError ( _ , _ , let underlyingError ) : throw underlyingError ? ? error
2019-08-29 07:52:51 +02:00
default : throw error
}
}
}
2019-05-08 08:02:53 +02:00
}