|
|
|
@ -1,6 +1,8 @@
|
|
|
|
|
|
|
|
|
|
extension LokiAPI {
|
|
|
|
|
@objc public class LokiP2PManager : NSObject {
|
|
|
|
|
private static let storage = OWSPrimaryStorage.shared()
|
|
|
|
|
private static let messageSender: MessageSender = SSKEnvironment.shared.messageSender
|
|
|
|
|
private static let ourHexEncodedPubKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey
|
|
|
|
|
|
|
|
|
|
/// The amount of time before pinging when a user is set to offline
|
|
|
|
|
private static let offlinePingTime = 2 * kMinuteInterval
|
|
|
|
@ -13,15 +15,77 @@ extension LokiAPI {
|
|
|
|
|
var timerDuration: Double
|
|
|
|
|
var pingTimer: Timer? = nil
|
|
|
|
|
|
|
|
|
|
var target: Target {
|
|
|
|
|
return Target(address: address, port: port)
|
|
|
|
|
var target: LokiAPI.Target {
|
|
|
|
|
return LokiAPI.Target(address: address, port: port)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static var ourP2PAddress: Target? = nil
|
|
|
|
|
/// Our p2p address
|
|
|
|
|
private static var ourP2PAddress: LokiAPI.Target? = nil
|
|
|
|
|
|
|
|
|
|
/// This is where we store the p2p details of our contacts
|
|
|
|
|
internal static var contactP2PDetails = [String: P2PDetails]()
|
|
|
|
|
private static var contactP2PDetails = [String: P2PDetails]()
|
|
|
|
|
|
|
|
|
|
// MARK: - Public functions
|
|
|
|
|
|
|
|
|
|
/// Set our local P2P address
|
|
|
|
|
///
|
|
|
|
|
/// - Parameter url: The url to our local server
|
|
|
|
|
@objc public static func setOurP2PAddress(url: URL) {
|
|
|
|
|
guard let scheme = url.scheme, let host = url.host, let port = url.port else { return }
|
|
|
|
|
let target = LokiAPI.Target(address: "\(scheme)://\(host)", port: UInt32(port))
|
|
|
|
|
ourP2PAddress = target
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Ping a contact
|
|
|
|
|
///
|
|
|
|
|
/// - Parameter pubKey: The contact hex pubkey
|
|
|
|
|
@objc(pingContact:)
|
|
|
|
|
public static func ping(contact pubKey: String) {
|
|
|
|
|
// Dispatch on the main queue so we escape any transaction blocks
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
guard let thread = TSContactThread.fetch(uniqueId: pubKey) else {
|
|
|
|
|
Logger.warn("[Loki][Ping] Failed to fetch thread for \(pubKey)")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
guard let message = lokiAddressMessage(for: thread, isPing: true) else {
|
|
|
|
|
Logger.warn("[Loki][Ping] Failed to build ping message for \(pubKey)")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
messageSender.sendPromise(message: message).retainUntilComplete()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Broadcash an online message to all our friends.
|
|
|
|
|
/// This shouldn't be called inside a transaction.
|
|
|
|
|
@objc public static func broadcastOnlineStatus() {
|
|
|
|
|
// Escape any transaction blocks
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
let friendThreads = getAllFriendThreads()
|
|
|
|
|
for thread in friendThreads {
|
|
|
|
|
sendOnlineBroadcastMessage(forThread: thread)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - Internal functions
|
|
|
|
|
|
|
|
|
|
/// Get the P2P details for the given contact.
|
|
|
|
|
///
|
|
|
|
|
/// - Parameter pubKey: The contact hex pubkey
|
|
|
|
|
/// - Returns: The P2P Details or nil if they don't exist
|
|
|
|
|
internal static func getDetails(forContact pubKey: String) -> P2PDetails? {
|
|
|
|
|
return contactP2PDetails[pubKey]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the `LokiAddressMessage` for the given thread.
|
|
|
|
|
///
|
|
|
|
|
/// - Parameter thread: The contact thread.
|
|
|
|
|
/// - Returns: The `LokiAddressMessage` for that thread.
|
|
|
|
|
@objc public static func onlineBroadcastMessage(forThread thread: TSThread) -> LokiAddressMessage? {
|
|
|
|
|
return lokiAddressMessage(for: thread, isPing: false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Handle P2P logic when we receive a `LokiAddressMessage`
|
|
|
|
|
///
|
|
|
|
@ -30,8 +94,9 @@ extension LokiAPI {
|
|
|
|
|
/// - address: The pther users p2p address
|
|
|
|
|
/// - port: The other users p2p port
|
|
|
|
|
/// - receivedThroughP2P: Wether we received the message through p2p
|
|
|
|
|
@objc public static func didReceiveLokiAddressMessage(forContact pubKey: String, address: String, port: UInt32, receivedThroughP2P: Bool, transaction: YapDatabaseReadTransaction) {
|
|
|
|
|
@objc internal static func didReceiveLokiAddressMessage(forContact pubKey: String, address: String, port: UInt32, receivedThroughP2P: Bool) {
|
|
|
|
|
// Stagger the ping timers so that contacts don't ping each other at the same time
|
|
|
|
|
|
|
|
|
|
let timerDuration = pubKey < ourHexEncodedPubKey ? 1 * kMinuteInterval : 2 * kMinuteInterval
|
|
|
|
|
|
|
|
|
|
// Get out current contact details
|
|
|
|
@ -67,10 +132,15 @@ extension LokiAPI {
|
|
|
|
|
3. We had the contact marked as offline, we need to make sure that we can reach their server.
|
|
|
|
|
4. The other contact details have changed, we need to make sure that we can reach their new server.
|
|
|
|
|
*/
|
|
|
|
|
ping(contact: pubKey, withTransaction: transaction)
|
|
|
|
|
ping(contact: pubKey)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc public static func setOnline(_ isOnline: Bool, forContact pubKey: String) {
|
|
|
|
|
/// Mark a contact as online or offline.
|
|
|
|
|
///
|
|
|
|
|
/// - Parameters:
|
|
|
|
|
/// - isOnline: Whether to set the contact to online or offline.
|
|
|
|
|
/// - pubKey: The contact hexh pubKey
|
|
|
|
|
@objc internal static func setOnline(_ isOnline: Bool, forContact pubKey: String) {
|
|
|
|
|
// Make sure we are on the main thread
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
guard var details = contactP2PDetails[pubKey] else { return }
|
|
|
|
@ -86,67 +156,9 @@ extension LokiAPI {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc public static func ping(contact pubKey: String) {
|
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
storage.dbReadConnection.read { transaction in
|
|
|
|
|
ping(contact: pubKey, withTransaction: transaction)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc public static func ping(contact pubKey: String, withTransaction transaction: YapDatabaseReadTransaction) {
|
|
|
|
|
guard let thread = TSContactThread.fetch(uniqueId: pubKey, transaction: transaction) else {
|
|
|
|
|
Logger.warn("[Loki][Ping] Failed to fetch thread for \(pubKey)")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
guard let message = lokiAddressMessage(for: thread, isPing: true) else {
|
|
|
|
|
Logger.warn("[Loki][Ping] Failed to build ping message for \(pubKey)")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
messageSender.sendPromise(message: message).retainUntilComplete()
|
|
|
|
|
}
|
|
|
|
|
// MARK: - Private functions
|
|
|
|
|
|
|
|
|
|
/// Set our local P2P address
|
|
|
|
|
///
|
|
|
|
|
/// - Parameter url: The url to our local server
|
|
|
|
|
@objc public static func setOurP2PAddress(url: URL) {
|
|
|
|
|
guard let scheme = url.scheme, let host = url.host, let port = url.port else { return }
|
|
|
|
|
let target = Target(address: "\(scheme)://\(host)", port: UInt32(port))
|
|
|
|
|
ourP2PAddress = target
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Broadcash an online message to all our friends.
|
|
|
|
|
/// This shouldn't be called inside a transaction.
|
|
|
|
|
@objc public static func broadcastOnlineStatus() {
|
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
|
|
let friendThreads = getAllFriendThreads()
|
|
|
|
|
for thread in friendThreads {
|
|
|
|
|
sendOnlineBroadcastMessage(forThread: thread)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the `LokiAddressMessage` for the given thread.
|
|
|
|
|
///
|
|
|
|
|
/// - Parameter thread: The contact thread.
|
|
|
|
|
/// - Returns: The `LokiAddressMessage` for that thread.
|
|
|
|
|
@objc public static func onlineBroadcastMessage(forThread thread: TSThread) -> LokiAddressMessage? {
|
|
|
|
|
return lokiAddressMessage(for: thread, isPing: false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static func lokiAddressMessage(for thread: TSThread, isPing: Bool) -> LokiAddressMessage? {
|
|
|
|
|
guard let ourAddress = ourP2PAddress else {
|
|
|
|
|
Logger.error("P2P Address not set")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return LokiAddressMessage(in: thread, address: ourAddress.address, port: ourAddress.port, isPing: isPing)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Send a `Loki Address` message to the given thread
|
|
|
|
|
///
|
|
|
|
|
/// - Parameter thread: The contact thread to send the message to
|
|
|
|
|
@objc public static func sendOnlineBroadcastMessage(forThread thread: TSContactThread) {
|
|
|
|
|
private static func sendOnlineBroadcastMessage(forThread thread: TSContactThread) {
|
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
|
|
guard let message = onlineBroadcastMessage(forThread: thread) else {
|
|
|
|
@ -159,13 +171,6 @@ extension LokiAPI {
|
|
|
|
|
}.retainUntilComplete()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc public static func sendOnlineBroadcastMessage(forThread thread: TSContactThread, transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
guard let ourAddress = ourP2PAddress else {
|
|
|
|
|
owsFailDebug("P2P Address not set")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static func getAllFriendThreads() -> [TSContactThread] {
|
|
|
|
|
var friendThreadIds = [String]()
|
|
|
|
|
TSContactThread.enumerateCollectionObjects { (object, _) in
|
|
|
|
@ -179,4 +184,12 @@ extension LokiAPI {
|
|
|
|
|
return friendThreadIds.compactMap { TSContactThread.fetch(uniqueId: $0) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static func lokiAddressMessage(for thread: TSThread, isPing: Bool) -> LokiAddressMessage? {
|
|
|
|
|
guard let ourAddress = ourP2PAddress else {
|
|
|
|
|
Logger.error("P2P Address not set")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return LokiAddressMessage(in: thread, address: ourAddress.address, port: ourAddress.port, isPing: isPing)
|
|
|
|
|
}
|
|
|
|
|
}
|