Fix multi device bugs

Various database handling issues, incorrect friend request status handling on auto-generated friend requests and refactor of the multi device message sending logic
This commit is contained in:
nielsandriesse 2020-04-29 12:11:12 +10:00
parent a9b9c807ff
commit 33e47298ae
14 changed files with 204 additions and 144 deletions

View File

@ -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?
}
}

View File

@ -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<String> {
if let token = getAuthTokenFromDatabase(for: server, in: transaction) {
internal static func getAuthToken(for server: String) -> Promise<String> {
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<String> in
let (promise, seal) = Promise<String>.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

View File

@ -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<Set<DeviceLink>> {
return getDeviceLinks(associatedWith: [ hexEncodedPublicKey ], in: transaction)
public static func getDeviceLinks(associatedWith hexEncodedPublicKey: String) -> Promise<Set<DeviceLink>> {
return getDeviceLinks(associatedWith: [ hexEncodedPublicKey ])
}
@objc(getDeviceLinksAssociatedWithHexEncodedPublicKeys:)
public static func objc_getDeviceLinks(associatedWith hexEncodedPublicKeys: Set<String>) -> 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<String>, in transaction: YapDatabaseReadWriteTransaction? = nil) -> Promise<Set<DeviceLink>> {
public static func getDeviceLinks(associatedWith hexEncodedPublicKeys: Set<String>) -> Promise<Set<DeviceLink>> {
let hexEncodedPublicKeysDescription = "[ \(hexEncodedPublicKeys.joined(separator: ", ")) ]"
print("[Loki] Getting device links for: \(hexEncodedPublicKeysDescription).")
return getAuthToken(for: server, in: transaction).then(on: LokiAPI.workQueue) { token -> Promise<Set<DeviceLink>> in
return getAuthToken(for: server).then(on: LokiAPI.workQueue) { token -> Promise<Set<DeviceLink>> 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<DeviceLink> in
storage.dbReadWriteConnection.readWrite { transaction in
storage.setDeviceLinks(deviceLinks, in: transaction)
}.then(on: LokiAPI.workQueue) { deviceLinks -> Promise<Set<DeviceLink>> in
let (promise, seal) = Promise<Set<DeviceLink>>.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<Void> in
let (promise, seal) = Promise<Void>.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<Void> in
let (promise, seal) = Promise<Void>.pending()
DispatchQueue.main.async {
storage.dbReadWriteConnection.readWrite { transaction in
storage.removeDeviceLink(deviceLink, in: transaction)
}
seal.fulfill(())
}
return promise
}
}

View File

@ -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<String>) {
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))
}
}
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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<TSContactThread>.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<OWSMessageSend> {
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<OWSMessageSend>.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<Set<MultiDeviceDestination>> {
// FIXME: Threading
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: .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

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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];
}