diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 88d06dc68..3c1573ab9 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -797,7 +797,7 @@ static BOOL isInternalTestVersion = NO; [self startOpenGroupPollersIfNeeded]; // Loki: Get device links - [[LKFileServerAPI getDeviceLinksAssociatedWith:userHexEncodedPublicKey] retainUntilComplete]; + [[LKFileServerAPI getDeviceLinksAssociatedWithHexEncodedPublicKey:userHexEncodedPublicKey] retainUntilComplete]; // Loki: Update profile picture if needed NSUserDefaults *userDefaults = NSUserDefaults.standardUserDefaults; @@ -1305,7 +1305,7 @@ static BOOL isInternalTestVersion = NO; [self startOpenGroupPollersIfNeeded]; // Loki: Get device links - [[LKFileServerAPI getDeviceLinksAssociatedWith:self.tsAccountManager.localNumber] retainUntilComplete]; // TODO: Is this even needed? + [[LKFileServerAPI getDeviceLinksAssociatedWithHexEncodedPublicKey:self.tsAccountManager.localNumber] retainUntilComplete]; // TODO: Is this even needed? } } diff --git a/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift b/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift index 97ec9face..6d766357b 100644 --- a/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiDotNetAPI.swift @@ -26,43 +26,33 @@ public class LokiDotNetAPI : NSObject { /// To be overridden by subclasses. internal class var authTokenCollection: String { preconditionFailure("authTokenCollection is abstract and must be overridden.") } - private static func getAuthTokenFromDatabase(for server: String, in transaction: YapDatabaseReadTransaction? = nil) -> String? { - func getAuthTokenInternal(in transaction: YapDatabaseReadTransaction) -> String? { - return transaction.object(forKey: server, inCollection: authTokenCollection) as! String? - } - if let transaction = transaction { - return getAuthTokenInternal(in: transaction) - } else { - var result: String? = nil - storage.dbReadConnection.read { transaction in - result = getAuthTokenInternal(in: transaction) - } - return result + private static func getAuthTokenFromDatabase(for server: String) -> String? { + var result: String? = nil + storage.dbReadConnection.read { transaction in + result = transaction.object(forKey: server, inCollection: authTokenCollection) as! String? } + return result } - internal static func getAuthToken(for server: String, in transaction: YapDatabaseReadWriteTransaction? = nil) -> Promise { - if let token = getAuthTokenFromDatabase(for: server, in: transaction) { + internal static func getAuthToken(for server: String) -> Promise { + if let token = getAuthTokenFromDatabase(for: server) { return Promise.value(token) } else { - return requestNewAuthToken(for: server).then(on: LokiAPI.workQueue) { submitAuthToken($0, for: server) }.map { token -> String in - setAuthToken(for: server, to: token, in: transaction) // TODO: Does keeping the transaction this long even make sense? - return token + return requestNewAuthToken(for: server).then(on: LokiAPI.workQueue) { submitAuthToken($0, for: server) }.then(on: LokiAPI.workQueue) { token -> Promise in + let (promise, seal) = Promise.pending() + DispatchQueue.main.async { + storage.dbReadWriteConnection.readWrite { transaction in + setAuthToken(for: server, to: token, in: transaction) + } + seal.fulfill(token) + } + return promise } } } - private static func setAuthToken(for server: String, to newValue: String, in transaction: YapDatabaseReadWriteTransaction? = nil) { - func setAuthTokenInternal(in transaction: YapDatabaseReadWriteTransaction) { - transaction.setObject(newValue, forKey: server, inCollection: authTokenCollection) - } - if let transaction = transaction, transaction.connection.pendingTransactionCount != 0 { - setAuthTokenInternal(in: transaction) - } else { - storage.dbReadWriteConnection.readWrite { transaction in - setAuthTokenInternal(in: transaction) - } - } + private static func setAuthToken(for server: String, to newValue: String, in transaction: YapDatabaseReadWriteTransaction) { + transaction.setObject(newValue, forKey: server, inCollection: authTokenCollection) } // MARK: Lifecycle diff --git a/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift b/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift index d53b8dc45..0f4409c14 100644 --- a/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiFileServerAPI.swift @@ -17,23 +17,28 @@ public final class LokiFileServerAPI : LokiDotNetAPI { override internal class var authTokenCollection: String { return "LokiStorageAuthTokenCollection" } // MARK: Device Links - @objc(getDeviceLinksAssociatedWith:) + @objc(getDeviceLinksAssociatedWithHexEncodedPublicKey:) public static func objc_getDeviceLinks(associatedWith hexEncodedPublicKey: String) -> AnyPromise { return AnyPromise.from(getDeviceLinks(associatedWith: hexEncodedPublicKey)) } /// Gets the device links associated with the given hex encoded public key from the /// server and stores and returns the valid ones. - public static func getDeviceLinks(associatedWith hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction? = nil) -> Promise> { - return getDeviceLinks(associatedWith: [ hexEncodedPublicKey ], in: transaction) + public static func getDeviceLinks(associatedWith hexEncodedPublicKey: String) -> Promise> { + return getDeviceLinks(associatedWith: [ hexEncodedPublicKey ]) + } + + @objc(getDeviceLinksAssociatedWithHexEncodedPublicKeys:) + public static func objc_getDeviceLinks(associatedWith hexEncodedPublicKeys: Set) -> AnyPromise { + return AnyPromise.from(getDeviceLinks(associatedWith: hexEncodedPublicKeys)) } /// Gets the device links associated with the given hex encoded public keys from the /// server and stores and returns the valid ones. - public static func getDeviceLinks(associatedWith hexEncodedPublicKeys: Set, in transaction: YapDatabaseReadWriteTransaction? = nil) -> Promise> { + public static func getDeviceLinks(associatedWith hexEncodedPublicKeys: Set) -> Promise> { let hexEncodedPublicKeysDescription = "[ \(hexEncodedPublicKeys.joined(separator: ", ")) ]" print("[Loki] Getting device links for: \(hexEncodedPublicKeysDescription).") - return getAuthToken(for: server, in: transaction).then(on: LokiAPI.workQueue) { token -> Promise> in + return getAuthToken(for: server).then(on: LokiAPI.workQueue) { token -> Promise> in let queryParameters = "ids=\(hexEncodedPublicKeys.map { "@\($0)" }.joined(separator: ","))&include_user_annotations=1" let url = URL(string: "\(server)/users?\(queryParameters)")! let request = TSRequest(url: url) @@ -79,11 +84,15 @@ public final class LokiFileServerAPI : LokiDotNetAPI { return deviceLink } }) - }.map(on: LokiAPI.workQueue) { deviceLinks -> Set in - storage.dbReadWriteConnection.readWrite { transaction in - storage.setDeviceLinks(deviceLinks, in: transaction) + }.then(on: LokiAPI.workQueue) { deviceLinks -> Promise> in + let (promise, seal) = Promise>.pending() + DispatchQueue.main.async { + storage.dbReadWriteConnection.readWrite { transaction in + storage.setDeviceLinks(deviceLinks, in: transaction) + } + seal.fulfill(deviceLinks) } - return deviceLinks + return promise } } } @@ -115,10 +124,15 @@ public final class LokiFileServerAPI : LokiDotNetAPI { deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction) } deviceLinks.insert(deviceLink) - return setDeviceLinks(deviceLinks).map { - storage.dbReadWriteConnection.readWrite { transaction in - storage.addDeviceLink(deviceLink, in: transaction) + return setDeviceLinks(deviceLinks).then(on: LokiAPI.workQueue) { _ -> Promise in + let (promise, seal) = Promise.pending() + DispatchQueue.main.async { + storage.dbReadWriteConnection.readWrite { transaction in + storage.addDeviceLink(deviceLink, in: transaction) + } + seal.fulfill(()) } + return promise } } @@ -129,10 +143,15 @@ public final class LokiFileServerAPI : LokiDotNetAPI { deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction) } deviceLinks.remove(deviceLink) - return setDeviceLinks(deviceLinks).map { - storage.dbReadWriteConnection.readWrite { transaction in - storage.removeDeviceLink(deviceLink, in: transaction) + return setDeviceLinks(deviceLinks).then(on: LokiAPI.workQueue) { _ -> Promise in + let (promise, seal) = Promise.pending() + DispatchQueue.main.async { + storage.dbReadWriteConnection.readWrite { transaction in + storage.removeDeviceLink(deviceLink, in: transaction) + } + seal.fulfill(()) } + return promise } } diff --git a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift index 14f27c309..7afe0227a 100644 --- a/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Closed Groups/ClosedGroupsProtocol.swift @@ -29,20 +29,38 @@ public final class ClosedGroupsProtocol : NSObject { guard let thread = thread else { return false } // The envelope source is set during UD decryption let hexEncodedPublicKey = envelope.source! - return !thread.isUserAdmin(inGroup: hexEncodedPublicKey, transaction: transaction) // TODO: I wonder how this was happening in the first place? + return !thread.isUserAdmin(inGroup: hexEncodedPublicKey, transaction: transaction) } - @objc(establishSessionsIfNeededWithClosedGroupMembers:in:using:) - public static func establishSessionsIfNeeded(with closedGroupMembers: [String], in thread: TSGroupThread, using transaction: YapDatabaseReadWriteTransaction) { - closedGroupMembers.forEach { member in - guard member != getUserHexEncodedPublicKey() else { return } - let hasSession = storage.containsSession(member, deviceId: 1, protocolContext: transaction) // TODO: Instead of 1 we should use the primary device ID thingy - if hasSession { return } - let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) - let sessionRequestMessage = SessionRequestMessage(thread: thread) - // TODO: I don't think this works correctly with multi device - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - messageSenderJobQueue.add(message: sessionRequestMessage, transaction: transaction) + @objc(establishSessionsIfNeededWithClosedGroupMembers:in:) + public static func establishSessionsIfNeeded(with closedGroupMembers: [String], in thread: TSGroupThread) { + func establishSessionsIfNeeded(with hexEncodedPublicKeys: Set) { + storage.dbReadWriteConnection.readWrite { transaction in + hexEncodedPublicKeys.forEach { hexEncodedPublicKey in + guard hexEncodedPublicKey != getUserHexEncodedPublicKey() else { return } + let hasSession = storage.containsSession(hexEncodedPublicKey, deviceId: Int32(OWSDevicePrimaryDeviceId), protocolContext: transaction) + guard !hasSession else { return } + let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction) + let sessionRequestMessage = SessionRequestMessage(thread: thread) + let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue + messageSenderJobQueue.add(message: sessionRequestMessage, transaction: transaction) + } + } + } + // We could just let the multi device message sending logic take care of slave devices, but that'd mean + // making a request to the file server for each member involved. With the line below we (hopefully) reduce + // that to one request. + LokiFileServerAPI.getDeviceLinks(associatedWith: Set(closedGroupMembers)).map { + Set($0.flatMap { [ $0.master.hexEncodedPublicKey, $0.slave.hexEncodedPublicKey ] }).union(closedGroupMembers) + }.done { hexEncodedPublicKeys in + DispatchQueue.main.async { + establishSessionsIfNeeded(with: hexEncodedPublicKeys) + } + }.catch { _ in + // Try the inefficient way if the file server failed + DispatchQueue.main.async { + establishSessionsIfNeeded(with: Set(closedGroupMembers)) + } } } } diff --git a/SignalServiceKit/src/Loki/Protocol/Friend Requests/FriendRequestProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Friend Requests/FriendRequestProtocol.swift index f0509de66..67265fd51 100644 --- a/SignalServiceKit/src/Loki/Protocol/Friend Requests/FriendRequestProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Friend Requests/FriendRequestProtocol.swift @@ -65,8 +65,8 @@ public final class FriendRequestProtocol : NSObject { sendFriendRequestAcceptanceMessage(to: thread.contactIdentifier(), in: thread, using: transaction) // NOT hexEncodedPublicKey thread.saveFriendRequestStatus(.friends, with: transaction) } else { - let autoGeneratedFRMessageSend = MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessageSend(for: thread.contactIdentifier(), in: transaction) // NOT hexEncodedPublicKey - OWSDispatch.sendingQueue().async { + MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessageSend(for: thread.contactIdentifier(), in: transaction) // NOT hexEncodedPublicKey + .done(on: OWSDispatch.sendingQueue()) { autoGeneratedFRMessageSend in let messageSender = SSKEnvironment.shared.messageSender messageSender.sendMessage(autoGeneratedFRMessageSend) } diff --git a/SignalServiceKit/src/Loki/Protocol/Mentions/MentionsManager.swift b/SignalServiceKit/src/Loki/Protocol/Mentions/MentionsManager.swift index b56f876b9..aabbc549c 100644 --- a/SignalServiceKit/src/Loki/Protocol/Mentions/MentionsManager.swift +++ b/SignalServiceKit/src/Loki/Protocol/Mentions/MentionsManager.swift @@ -79,7 +79,7 @@ public final class MentionsManager : NSObject { if let transaction = transaction { populate(in: transaction) } else { - storage.dbReadWriteConnection.readWrite { transaction in + storage.dbReadConnection.read { transaction in populate(in: transaction) } } diff --git a/SignalServiceKit/src/Loki/Protocol/Multi Device/MultiDeviceProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Multi Device/MultiDeviceProtocol.swift index 6f9c8d939..f93716b21 100644 --- a/SignalServiceKit/src/Loki/Protocol/Multi Device/MultiDeviceProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Multi Device/MultiDeviceProtocol.swift @@ -29,9 +29,7 @@ public final class MultiDeviceProtocol : NSObject { // MARK: - Multi Device Destination public struct MultiDeviceDestination : Hashable { public let hexEncodedPublicKey: String - public let kind: Kind - - public enum Kind : String { case master, slave } + public let isMaster: Bool } // MARK: - Initialization @@ -40,65 +38,81 @@ public final class MultiDeviceProtocol : NSObject { // MARK: - Sending (Part 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 sendMessage(_ messageSend: OWSMessageSend, to destination: MultiDeviceDestination, in transaction: YapDatabaseReadTransaction) { + let (promise, seal) = Promise.pending() + let message = messageSend.message + let messageSender = SSKEnvironment.shared.messageSender + 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 { + DispatchQueue.main.async { + storage.dbReadWriteConnection.readWrite { transaction in + getAutoGeneratedMultiDeviceFRMessageSend(for: destination.hexEncodedPublicKey, in: transaction) + .done(on: OWSDispatch.sendingQueue()) { autoGeneratedFRMessageSend in + messageSender.sendMessage(autoGeneratedFRMessageSend) + } + } + } + } + } + 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 { + DispatchQueue.main.async { + storage.dbReadWriteConnection.readWrite { transaction in + let thread = TSContactThread.getOrCreateThread(withContactId: destination.hexEncodedPublicKey, transaction: transaction) + seal.fulfill(thread) + } + } + } } @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 + 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: This seems really iffy - 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 - // Send to master destination - if let masterDestination = destinations.first(where: { $0.kind == .master }) { - let thread = TSContactThread.getOrCreateThread(contactId: masterDestination.hexEncodedPublicKey) // TODO: I guess it's okay this starts a new transaction? - 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: Yet another transaction - frMessageSend = getAutoGeneratedMultiDeviceFRMessageSend(for: masterDestination.hexEncodedPublicKey, in: transaction) - } - messageSender.sendMessage(frMessageSend) + let masterDestination = destinations.first { $0.isMaster } + if let masterDestination = masterDestination { + storage.dbReadConnection.read { transaction in + sendMessage(messageSend, to: masterDestination, in: transaction) } } - // Send to slave destinations (using a best attempt approach (i.e. ignoring the message send result) for now) - let slaveDestinations = destinations.filter { $0.kind == .slave } - for slaveDestination in slaveDestinations { - let thread = TSContactThread.getOrCreateThread(contactId: slaveDestination.hexEncodedPublicKey) // TODO: I guess it's okay this starts a new transaction? - if thread.isContactFriend || isSilentMessage || isFriendRequestMessage || isSessionRequestMessage || isGroupMessage { - let messageSendCopy = messageSend.copy(with: slaveDestination) - messageSender.sendMessage(messageSendCopy) - } else { - var frMessageSend: OWSMessageSend! - // FIXME: This crashes sometimes due to transaction nesting - storage.dbReadWriteConnection.readWrite { transaction in // TODO: Yet another transaction - frMessageSend = getAutoGeneratedMultiDeviceFRMessageSend(for: slaveDestination.hexEncodedPublicKey, in: transaction) - } - messageSender.sendMessage(frMessageSend) + 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 - // Proceed even if updating the linked devices map failed so that message sending - // is independent of whether the file server is up + // Proceed even if updating the recipient's device links failed, so that message sending + // is independent of whether the file server is online 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: YapDatabaseReadTransaction) -> 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: Could we make this computed? - if thread.friendRequestStatus == .none || thread.friendRequestStatus == .requestExpired { - thread.saveFriendRequestStatus(.requestSent, with: transaction) // TODO: Should we always immediately mark the slave device as a friend? - } 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 { 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() - var recipientUDAccess: OWSUDAccess? - if let senderCertificate = senderCertificate { - recipientUDAccess = udManager.udAccess(forRecipientId: hexEncodedPublicKey, requireSyncAccess: true) + let (promise, seal) = Promise.pending() + DispatchQueue.main.async { + var recipientUDAccess: OWSUDAccess? + if let senderCertificate = senderCertificate { + recipientUDAccess = udManager.udAccess(forRecipientId: hexEncodedPublicKey, requireSyncAccess: true) + } + 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 OWSMessageSend(message: message, thread: thread, recipient: recipient, senderCertificate: senderCertificate, - udAccess: recipientUDAccess, localNumber: getUserHexEncodedPublicKey(), success: { - - }, failure: { _ in - - }) + return promise } // MARK: - Receiving @@ -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 { // Here (in a non-@objc extension) because it doesn't interoperate well with Obj-C public extension MultiDeviceProtocol { - fileprivate static func getMultiDeviceDestinations(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> Promise> { - // FIXME: Threading + fileprivate static func getMultiDeviceDestinations(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> Promise> { let (promise, seal) = Promise>.pending() func getDestinations(in transaction: YapDatabaseReadTransaction? = nil) { storage.dbReadConnection.read { transaction in var destinations: Set = [] 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: .slave) } + let slaveDestinations = deviceLinks.map { MultiDeviceDestination(hexEncodedPublicKey: $0.slave.hexEncodedPublicKey, isMaster: false) } 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 diff --git a/SignalServiceKit/src/Loki/Protocol/Session Management/LokiSessionResetImplementation.swift b/SignalServiceKit/src/Loki/Protocol/Session Management/LokiSessionResetImplementation.swift index 7c08d0d1e..9be9218c7 100644 --- a/SignalServiceKit/src/Loki/Protocol/Session Management/LokiSessionResetImplementation.swift +++ b/SignalServiceKit/src/Loki/Protocol/Session Management/LokiSessionResetImplementation.swift @@ -15,7 +15,7 @@ public class LokiSessionResetImplementation : NSObject, SessionResetProtocol { } public func validatePreKeyForFriendRequestAcceptance(for recipientID: String, whisperMessage: CipherMessage, protocolContext: Any?) throws { - guard let transaction = protocolContext as? YapDatabaseReadWriteTransaction else { + guard let transaction = protocolContext as? YapDatabaseReadTransaction else { print("[Loki] Couldn't verify friend request acceptance pre key because an invalid transaction was provided.") return } @@ -31,7 +31,7 @@ public class LokiSessionResetImplementation : NSObject, SessionResetProtocol { } public func getSessionResetStatus(for recipientID: String, protocolContext: Any?) -> SessionResetStatus { - guard let transaction = protocolContext as? YapDatabaseReadWriteTransaction else { + guard let transaction = protocolContext as? YapDatabaseReadTransaction else { print("[Loki] Couldn't get session reset status for \(recipientID) because an invalid transaction was provided.") return .none } diff --git a/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift index 57633aa7f..425aa5446 100644 --- a/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Session Management/SessionManagementProtocol.swift @@ -147,7 +147,6 @@ public final class SessionManagementProtocol : NSObject { thread.addSessionRestoreDevice(hexEncodedPublicKey, transaction: transaction) default: break } - } @objc(isSessionRestoreMessage:) @@ -175,11 +174,11 @@ public final class SessionManagementProtocol : NSObject { return } storage.setPreKeyBundle(preKeyBundle, forContact: hexEncodedPublicKey, transaction: transaction) - // If we received a friend request (i.e. also a new pre key bundle), but we were already friends with the other user, reset the session - // The envelope type is set during UD decryption + // If we received a friend request (i.e. also a new pre key bundle), but we were already friends with the other user, reset the session. + // The envelope type is set during UD decryption. if envelope.type == .friendRequest, - let thread = TSContactThread.getWithContactId(hexEncodedPublicKey, transaction: transaction), - thread.isContactFriend { // TODO: Maybe this should be getOrCreate? + let thread = TSContactThread.getWithContactId(hexEncodedPublicKey, transaction: transaction), // TODO: Maybe this should be getOrCreate? + thread.isContactFriend { receiving_startSessionReset(in: thread, using: transaction) // Notify our other devices that we've started a session reset let syncManager = SSKEnvironment.shared.syncManager diff --git a/SignalServiceKit/src/Loki/Protocol/Sync Messages/SyncMessagesProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Sync Messages/SyncMessagesProtocol.swift index 87e34a3fa..5a62f14bc 100644 --- a/SignalServiceKit/src/Loki/Protocol/Sync Messages/SyncMessagesProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Sync Messages/SyncMessagesProtocol.swift @@ -136,7 +136,7 @@ public final class SyncMessagesProtocol : NSObject { newGroupThread.groupModel = newGroupModel // TODO: Should this use the setGroupModel method on TSGroupThread? newGroupThread.save(with: transaction) // Try to establish sessions with all members for which none exists yet when a group is created or updated - ClosedGroupsProtocol.establishSessionsIfNeeded(with: members, in: newGroupThread, using: transaction) + ClosedGroupsProtocol.establishSessionsIfNeeded(with: members, in: newGroupThread) OWSDisappearingMessagesJob.shared().becomeConsistent(withDisappearingDuration: transcript.dataMessage.expireTimer, thread: newGroupThread, createdByRemoteRecipientId: nil, createdInExistingGroup: true, transaction: transaction) let groupUpdatedMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: newGroupThread, messageType: .typeGroupUpdate, customMessage: groupUpdatedMessageDescription) groupUpdatedMessage.save(with: transaction) @@ -173,6 +173,7 @@ public final class SyncMessagesProtocol : NSObject { case .none: let messageSender = SSKEnvironment.shared.messageSender let autoGeneratedFRMessage = MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessage(for: hexEncodedPublicKey, in: transaction) + thread.friendRequestStatus = .requestSending thread.isForceHidden = true thread.save(with: transaction) // This takes into account multi device @@ -180,6 +181,7 @@ public final class SyncMessagesProtocol : NSObject { DispatchQueue.main.async { storage.dbReadWriteConnection.readWrite { transaction in autoGeneratedFRMessage.remove(with: transaction) + thread.friendRequestStatus = .requestSent thread.isForceHidden = false thread.save(with: transaction) } @@ -188,6 +190,7 @@ public final class SyncMessagesProtocol : NSObject { DispatchQueue.main.async { storage.dbReadWriteConnection.readWrite { transaction in autoGeneratedFRMessage.remove(with: transaction) + thread.friendRequestStatus = .none thread.isForceHidden = false thread.save(with: transaction) } @@ -218,7 +221,7 @@ public final class SyncMessagesProtocol : NSObject { thread = TSGroupThread.getOrCreateThread(with: groupModel, transaction: transaction) thread.save(with: transaction) } - ClosedGroupsProtocol.establishSessionsIfNeeded(with: groupModel.groupMemberIds, in: thread, using: transaction) + ClosedGroupsProtocol.establishSessionsIfNeeded(with: groupModel.groupMemberIds, in: thread) let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeGroupUpdate, customMessage: "You have joined the group.") infoMessage.save(with: transaction) } diff --git a/SignalServiceKit/src/Loki/Protocol/TTL.swift b/SignalServiceKit/src/Loki/Protocol/TTL.swift index acf4b6d2c..552f791ba 100644 --- a/SignalServiceKit/src/Loki/Protocol/TTL.swift +++ b/SignalServiceKit/src/Loki/Protocol/TTL.swift @@ -26,7 +26,7 @@ public final class TTLUtilities : NSObject { case .sessionRequest: return 4 * kDayInMs - 1 * kHourInMs case .regular: return 2 * kDayInMs case .typingIndicator: return 1 * kMinuteInMs - case .unlinkDevice: return 4 * kDayInMs + case .unlinkDevice: return 4 * kDayInMs - 1 * kHourInMs } } } diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 0911fec3c..0886bb38f 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -1326,7 +1326,7 @@ NS_ASSUME_NONNULL_BEGIN BOOL wasCurrentUserRemovedFromGroup = [removedMemberIds containsObject:userMasterHexEncodedPublicKey]; if (!wasCurrentUserRemovedFromGroup) { // Loki: Try to establish sessions with all members when a group is created or updated - [LKClosedGroupsProtocol establishSessionsIfNeededWithClosedGroupMembers:newMemberIds.allObjects in:newGroupThread using:transaction]; + [LKClosedGroupsProtocol establishSessionsIfNeededWithClosedGroupMembers:newMemberIds.allObjects in:newGroupThread]; } [[OWSDisappearingMessagesJob sharedJob] becomeConsistentWithDisappearingDuration:dataMessage.expireTimer diff --git a/SignalServiceKit/src/Messages/OWSMessageSend.swift b/SignalServiceKit/src/Messages/OWSMessageSend.swift index bf51a90fb..00cbf59c2 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSend.swift +++ b/SignalServiceKit/src/Messages/OWSMessageSend.swift @@ -99,8 +99,8 @@ public class OWSMessageSend: NSObject { OWSPrimaryStorage.shared().dbReadConnection.read { transaction in recipient = SignalRecipient.getOrBuildUnsavedRecipient(forRecipientId: destination.hexEncodedPublicKey, transaction: transaction) } - let success = (destination.kind == .master) ? self.success : { } - let failure = (destination.kind == .master) ? self.failure : { _ in } + let success = destination.isMaster ? self.success : { } + let failure = destination.isMaster ? self.failure : { _ in } return OWSMessageSend(message: message, thread: thread, recipient: recipient, senderCertificate: senderCertificate, udAccess: udAccess, localNumber: localNumber, success: success, failure: failure) } } diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 4239ae5c8..0a9a92e81 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -593,9 +593,11 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; }]; if ([LKMultiDeviceProtocol isMultiDeviceRequiredForMessage:message]) { // Avoid the write transaction if possible - [self.primaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [LKMultiDeviceProtocol sendMessageToDestinationAndLinkedDevices:messageSend in:transaction]; - }]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.primaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [LKMultiDeviceProtocol sendMessageToDestinationAndLinkedDevices:messageSend in:transaction]; + }]; + }); } else { [self sendMessage:messageSend]; } @@ -1565,9 +1567,11 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; failure(error); }]; if ([LKMultiDeviceProtocol isMultiDeviceRequiredForMessage:message]) { // Avoid the write transaction if possible - [self.primaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [LKMultiDeviceProtocol sendMessageToDestinationAndLinkedDevices:messageSend in:transaction]; - }]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.primaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [LKMultiDeviceProtocol sendMessageToDestinationAndLinkedDevices:messageSend in:transaction]; + }]; + }); } else { [self sendMessage:messageSend]; }