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:
parent
a9b9c807ff
commit
33e47298ae
|
@ -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?
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue