|
|
|
@ -9,14 +9,14 @@ import PromiseKit
|
|
|
|
|
|
|
|
|
|
// TODO: Document the expected cases for everything and then express those cases in tests
|
|
|
|
|
|
|
|
|
|
public extension SessionProtocol {
|
|
|
|
|
@objc public extension SessionProtocol {
|
|
|
|
|
|
|
|
|
|
// MARK: - Message Destination
|
|
|
|
|
@objc(getDestinationsForOutgoingSyncMessage:)
|
|
|
|
|
public static func getDestinations(for outgoingSyncMessage: OWSOutgoingSyncMessage) -> Set<String> {
|
|
|
|
|
@objc(getDestinationsForOutgoingSyncMessage)
|
|
|
|
|
public static func getDestinationsForOutgoingSyncMessage() -> Set<String> {
|
|
|
|
|
var result: Set<String> = []
|
|
|
|
|
storage.dbReadConnection.read { transaction in
|
|
|
|
|
// NOTE: Aim the message at all linked devices, including this one
|
|
|
|
|
// Aim the message at all linked devices, including this one
|
|
|
|
|
// TODO: Should we exclude the current device?
|
|
|
|
|
result = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction)
|
|
|
|
|
}
|
|
|
|
@ -37,7 +37,6 @@ public extension SessionProtocol {
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
result = Set(outgoingGroupMessage.sendingRecipientIds()).intersection(thread.groupModel.groupMemberIds) // This is what Signal does
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
@ -115,7 +114,7 @@ public extension SessionProtocol {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - Multi Device
|
|
|
|
|
// MARK: - Multi Device (Part 1)
|
|
|
|
|
@objc(sendMessageToDestinationAndLinkedDevices:in:)
|
|
|
|
|
public static func sendMessageToDestinationAndLinkedDevices(_ messageSend: OWSMessageSend, in transaction: YapDatabaseReadWriteTransaction) {
|
|
|
|
|
// TODO: I'm pretty sure there are quite a few holes in this logic
|
|
|
|
@ -175,48 +174,6 @@ public extension SessionProtocol {
|
|
|
|
|
return AnyPromise.from(promise)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static func getMultiDeviceDestinations(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> Promise<Set<MultiDeviceDestination>> {
|
|
|
|
|
// FIXME: Threading
|
|
|
|
|
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)
|
|
|
|
|
destinations.insert(masterDestination)
|
|
|
|
|
let deviceLinks = storage.getDeviceLinks(for: masterHexEncodedPublicKey, in: transaction)
|
|
|
|
|
let slaveDestinations = deviceLinks.map { MultiDeviceDestination(hexEncodedPublicKey: $0.slave.hexEncodedPublicKey, kind: .slave) }
|
|
|
|
|
destinations.formUnion(slaveDestinations)
|
|
|
|
|
seal.fulfill(destinations)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let timeSinceLastUpdate: TimeInterval
|
|
|
|
|
if let lastDeviceLinkUpdate = lastDeviceLinkUpdate[hexEncodedPublicKey] {
|
|
|
|
|
timeSinceLastUpdate = Date().timeIntervalSince(lastDeviceLinkUpdate)
|
|
|
|
|
} else {
|
|
|
|
|
timeSinceLastUpdate = .infinity
|
|
|
|
|
}
|
|
|
|
|
if timeSinceLastUpdate > deviceLinkUpdateInterval {
|
|
|
|
|
let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey
|
|
|
|
|
LokiFileServerAPI.getDeviceLinks(associatedWith: masterHexEncodedPublicKey, in: transaction).done(on: LokiAPI.workQueue) { _ in
|
|
|
|
|
getDestinations()
|
|
|
|
|
lastDeviceLinkUpdate[hexEncodedPublicKey] = Date()
|
|
|
|
|
}.catch(on: LokiAPI.workQueue) { error in
|
|
|
|
|
if (error as? LokiDotNetAPI.LokiDotNetAPIError) == LokiDotNetAPI.LokiDotNetAPIError.parsingFailed {
|
|
|
|
|
// Don't immediately re-fetch in case of failure due to a parsing error
|
|
|
|
|
lastDeviceLinkUpdate[hexEncodedPublicKey] = Date()
|
|
|
|
|
getDestinations()
|
|
|
|
|
} else {
|
|
|
|
|
print("[Loki] Failed to get device links due to error: \(error).")
|
|
|
|
|
seal.reject(error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
getDestinations()
|
|
|
|
|
}
|
|
|
|
|
return promise
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc(getAutoGeneratedMultiDeviceFRMessageForHexEncodedPublicKey:in:)
|
|
|
|
|
public static func getAutoGeneratedMultiDeviceFRMessage(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> FriendRequestMessage {
|
|
|
|
|
let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
|
|
|
|
@ -325,6 +282,7 @@ public extension SessionProtocol {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - Typing Indicators
|
|
|
|
|
@objc(shouldSendTypingIndicatorForThread:)
|
|
|
|
|
public static func shouldSendTypingIndicator(for thread: TSThread) -> Bool {
|
|
|
|
|
return !thread.isGroupThread() && !isMessageNoteToSelf(thread)
|
|
|
|
|
}
|
|
|
|
@ -355,6 +313,7 @@ public extension SessionProtocol {
|
|
|
|
|
|
|
|
|
|
// TODO: Does the above make sense?
|
|
|
|
|
|
|
|
|
|
@objc(createPreKeys)
|
|
|
|
|
public static func createPreKeys() {
|
|
|
|
|
// We don't generate PreKeyRecords here.
|
|
|
|
|
// This is because we need the records to be linked to a contact since we don't have a central server.
|
|
|
|
@ -367,6 +326,7 @@ public extension SessionProtocol {
|
|
|
|
|
print("[Loki] Pre keys created successfully.")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc(refreshPreKeys)
|
|
|
|
|
public static func refreshPreKeys() {
|
|
|
|
|
guard storage.currentSignedPrekeyId() == nil else {
|
|
|
|
|
print("[Loki] Skipping pre key refresh; using existing signed pre key.")
|
|
|
|
@ -381,6 +341,7 @@ public extension SessionProtocol {
|
|
|
|
|
print("[Loki] Pre keys refreshed successfully.")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc(rotatePreKeys)
|
|
|
|
|
public static func rotatePreKeys() {
|
|
|
|
|
let signedPreKeyRecord = storage.generateRandomSignedRecord()
|
|
|
|
|
signedPreKeyRecord.markAsAcceptedByService()
|
|
|
|
@ -391,6 +352,7 @@ public extension SessionProtocol {
|
|
|
|
|
print("[Loki] Pre keys rotated successfully.")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@objc(shouldUseFallbackEncryptionForMessage:)
|
|
|
|
|
public static func shouldUseFallbackEncryption(_ message: TSOutgoingMessage) -> Bool {
|
|
|
|
|
return !isSessionRequired(for: message)
|
|
|
|
|
}
|
|
|
|
@ -403,3 +365,52 @@ public extension SessionProtocol {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - Multi Device (Part 2)
|
|
|
|
|
// Here (in a non-@objc extension) because it doesn't interoperate well with Obj-C
|
|
|
|
|
public extension SessionProtocol {
|
|
|
|
|
|
|
|
|
|
fileprivate static func getMultiDeviceDestinations(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> Promise<Set<MultiDeviceDestination>> {
|
|
|
|
|
// FIXME: Threading
|
|
|
|
|
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)
|
|
|
|
|
destinations.insert(masterDestination)
|
|
|
|
|
let deviceLinks = storage.getDeviceLinks(for: masterHexEncodedPublicKey, in: transaction)
|
|
|
|
|
let slaveDestinations = deviceLinks.map { MultiDeviceDestination(hexEncodedPublicKey: $0.slave.hexEncodedPublicKey, kind: .slave) }
|
|
|
|
|
destinations.formUnion(slaveDestinations)
|
|
|
|
|
seal.fulfill(destinations)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let timeSinceLastUpdate: TimeInterval
|
|
|
|
|
if let lastDeviceLinkUpdate = lastDeviceLinkUpdate[hexEncodedPublicKey] {
|
|
|
|
|
timeSinceLastUpdate = Date().timeIntervalSince(lastDeviceLinkUpdate)
|
|
|
|
|
} else {
|
|
|
|
|
timeSinceLastUpdate = .infinity
|
|
|
|
|
}
|
|
|
|
|
if timeSinceLastUpdate > deviceLinkUpdateInterval {
|
|
|
|
|
let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey
|
|
|
|
|
LokiFileServerAPI.getDeviceLinks(associatedWith: masterHexEncodedPublicKey, in: transaction).done(on: LokiAPI.workQueue) { _ in
|
|
|
|
|
getDestinations()
|
|
|
|
|
lastDeviceLinkUpdate[hexEncodedPublicKey] = Date()
|
|
|
|
|
}.catch(on: LokiAPI.workQueue) { error in
|
|
|
|
|
if (error as? LokiDotNetAPI.LokiDotNetAPIError) == LokiDotNetAPI.LokiDotNetAPIError.parsingFailed {
|
|
|
|
|
// Don't immediately re-fetch in case of failure due to a parsing error
|
|
|
|
|
lastDeviceLinkUpdate[hexEncodedPublicKey] = Date()
|
|
|
|
|
getDestinations()
|
|
|
|
|
} else {
|
|
|
|
|
print("[Loki] Failed to get device links due to error: \(error).")
|
|
|
|
|
seal.reject(error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
getDestinations()
|
|
|
|
|
}
|
|
|
|
|
return promise
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|