Merge branch 'dev' into friend-request-refactor

pull/175/head
Mikunj 4 years ago
commit 8fcb61353e

@ -1 +1 @@
Subproject commit 9d99b8d67e152d5ddbf58f260f677908c724a3b2
Subproject commit bfa486d0a82a6526220e7060fd87618a9cc28e85

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

@ -3,6 +3,7 @@
final class ConversationTitleView : UIView {
private let thread: TSThread
private var currentStatus: Status? { didSet { updateSubtitleForCurrentStatus() } }
private var handledMessageTimestamps: Set<NSNumber> = []
// MARK: Types
private enum Status : Int {
@ -112,6 +113,7 @@ final class ConversationTitleView : UIView {
@objc private func handleMessageSentNotification(_ notification: Notification) {
guard let timestamp = notification.object as? NSNumber else { return }
setStatusIfNeeded(to: .messageSent, forMessageWithTimestamp: timestamp)
handledMessageTimestamps.insert(timestamp)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.clearStatusIfNeededForMessageWithTimestamp(timestamp)
}
@ -123,12 +125,21 @@ final class ConversationTitleView : UIView {
}
private func setStatusIfNeeded(to status: Status, forMessageWithTimestamp timestamp: NSNumber) {
guard !handledMessageTimestamps.contains(timestamp) else { return }
var uncheckedTargetInteraction: TSInteraction? = nil
thread.enumerateInteractions { interaction in
guard interaction.timestamp == timestamp.uint64Value else { return }
uncheckedTargetInteraction = interaction
}
guard let targetInteraction = uncheckedTargetInteraction, targetInteraction.interactionType() == .outgoingMessage, status.rawValue > (currentStatus?.rawValue ?? 0) else { return }
guard let targetInteraction = uncheckedTargetInteraction, targetInteraction.interactionType() == .outgoingMessage,
status.rawValue > (currentStatus?.rawValue ?? 0), let hexEncodedPublicKey = targetInteraction.thread.contactIdentifier() else { return }
var masterHexEncodedPublicKey: String!
let storage = OWSPrimaryStorage.shared()
storage.dbReadConnection.read { transaction in
masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey
}
let isSlaveDevice = masterHexEncodedPublicKey != hexEncodedPublicKey
guard !isSlaveDevice else { return }
currentStatus = status
}

@ -220,6 +220,10 @@ typedef enum : NSUInteger {
@property (nonatomic) NSMutableArray<LKMention *> *mentions;
@property (nonatomic) NSString *oldText;
// Status bar updating
/// Used to avoid duplicate status bar updates.
@property (nonatomic) NSMutableSet<NSNumber *> *handledMessageTimestamps;
@end
#pragma mark -
@ -1237,9 +1241,11 @@ typedef enum : NSUInteger {
}
- (void)restoreSession {
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKSessionManagementProtocol sending_startSessionResetInThread:self.thread using:transaction];
}];
dispatch_async(dispatch_get_main_queue(), ^{
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKSessionManagementProtocol sending_startSessionResetInThread:self.thread using:transaction];
}];
});
}
- (void)noLongerVerifiedBannerViewWasTapped:(UIGestureRecognizer *)sender
@ -5396,6 +5402,7 @@ typedef enum : NSUInteger {
{
NSNumber *timestamp = (NSNumber *)notification.object;
[self setProgressIfNeededTo:1.0f forMessageWithTimestamp:timestamp];
[self.handledMessageTimestamps addObject:timestamp];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void) {
[self hideProgressIndicatorViewForMessageWithTimestamp:timestamp];
});
@ -5409,14 +5416,29 @@ typedef enum : NSUInteger {
- (void)setProgressIfNeededTo:(float)progress forMessageWithTimestamp:(NSNumber *)timestamp
{
__block TSInteraction *targetInteraction;
[self.thread enumerateInteractionsUsingBlock:^(TSInteraction *interaction) {
if (interaction.timestamp == timestamp.unsignedLongLongValue) {
targetInteraction = interaction;
}
}];
if (targetInteraction == nil || targetInteraction.interactionType != OWSInteractionType_OutgoingMessage) { return; }
if ([self.handledMessageTimestamps contains:^BOOL(NSNumber *t) {
return [t isEqual:timestamp];
}]) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
__block TSInteraction *targetInteraction;
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.thread enumerateInteractionsWithTransaction:transaction usingBlock:^(TSInteraction *interaction, YapDatabaseReadTransaction *t) {
if (interaction.timestamp == timestamp.unsignedLongLongValue) {
targetInteraction = interaction;
}
}];
}];
if (targetInteraction == nil || targetInteraction.interactionType != OWSInteractionType_OutgoingMessage) { return; }
NSString *hexEncodedPublicKey = targetInteraction.thread.contactIdentifier;
if (hexEncodedPublicKey == nil) { return; }
__block NSString *masterHexEncodedPublicKey;
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:hexEncodedPublicKey in:transaction] ?: hexEncodedPublicKey;
}];
BOOL isSlaveDevice = ![masterHexEncodedPublicKey isEqual:hexEncodedPublicKey];
if (isSlaveDevice) { return; }
if (progress <= self.progressIndicatorView.progress) { return; }
self.progressIndicatorView.alpha = 1;
[self.progressIndicatorView setProgress:progress animated:YES];

@ -2827,5 +2827,5 @@
"Please make sure the Session ID you entered is correct and try again." = "Please make sure the Session ID you entered is correct and try again.";
"Device Linking Failed" = "Device Linking Failed";
"Please check your internet connection and try again" = "Please check your internet connection and try again";
"Authorization Device Link" = "Authorization Device Link";
"Authorizing Device Link" = "Authorizing Device Link";
"Please wait while the device link is created. This can take up to a minute." = "Please wait while the device link is created. This can take up to a minute.";

@ -182,7 +182,8 @@ typedef void (^BuildOutgoingMessageCompletionBlock)(TSOutgoingMessage *savedMess
// Loki: If we're not friends then always set the message to a friend request message.
// If we're friends then the assumption is that we have the other user's pre key bundle.
NSString *messageClassAsString = (thread.isContactFriend || thread.isGroupThread) ? @"TSOutgoingMessage" : @"LKFriendRequestMessage";
BOOL isNoteToSelf = [LKSessionMetaProtocol isMessageNoteToSelf:thread];
NSString *messageClassAsString = (thread.isContactFriend || thread.isGroupThread || isNoteToSelf) ? @"TSOutgoingMessage" : @"LKFriendRequestMessage";
Class messageClass = NSClassFromString(messageClassAsString);
TSOutgoingMessage *message =

@ -216,7 +216,7 @@ ConversationColorName const kConversationColorName_Default = ConversationColorNa
if (!IsNoteToSelfEnabled()) {
return NO;
}
return [LKSessionProtocol isThreadNoteToSelf:self];
return [LKSessionMetaProtocol isThreadNoteToSelf:self];
}
#pragma mark - To be subclassed.

@ -2,7 +2,7 @@ import PromiseKit
// TODO: A lot of the API relies on things happening serially and state being maintained correctly (i.e. without
// race conditions). To this end we should just have one high quality serial queue and do everything on there, except
// for things that explicitly *can* be done in parallel and don't modify state, any which should then happen
// for things that explicitly *can* be done in parallel and don't modify state, which should then happen
// on a global queue.
@objc(LKAPI)

@ -26,43 +26,34 @@ 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()
// Dispatch async on the main queue to avoid nested write transactions
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,16 @@ 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()
// Dispatch async on the main queue to avoid nested write transactions
DispatchQueue.main.async {
storage.dbReadWriteConnection.readWrite { transaction in
storage.setDeviceLinks(deviceLinks, in: transaction)
}
seal.fulfill(deviceLinks)
}
return deviceLinks
return promise
}
}
}
@ -115,10 +125,16 @@ 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()
// Dispatch async on the main queue to avoid nested write transactions
DispatchQueue.main.async {
storage.dbReadWriteConnection.readWrite { transaction in
storage.addDeviceLink(deviceLink, in: transaction)
}
seal.fulfill(())
}
return promise
}
}
@ -129,10 +145,16 @@ 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()
// Dispatch async on the main queue to avoid nested write transactions
DispatchQueue.main.async {
storage.dbReadWriteConnection.readWrite { transaction in
storage.removeDeviceLink(deviceLink, in: transaction)
}
seal.fulfill(())
}
return promise
}
}

@ -7,9 +7,26 @@ public final class LokiPoller : NSObject {
private var hasStarted = false
private var hasStopped = false
private var usedSnodes = Set<LokiAPITarget>()
private var pollCount = 0
// MARK: Settings
private static let retryInterval: TimeInterval = 4
private static let retryInterval: TimeInterval = 1
/// After polling a given snode this many times we always switch to a new one.
///
/// The reason for doing this is that sometimes a snode will be giving us successful responses while
/// it isn't actually getting messages from other snodes.
private static let maxPollCount: UInt = 6
// MARK: Error
private enum Error : LocalizedError {
case pollLimitReached
var localizedDescription: String {
switch self {
case .pollLimitReached: return "Poll limit reached for current snode."
}
}
}
// MARK: Initialization
@objc public init(onMessagesReceived: @escaping ([SSKProtoEnvelope]) -> Void) {
@ -63,8 +80,12 @@ public final class LokiPoller : NSObject {
poll(nextSnode, seal: seal).done(on: LokiAPI.workQueue) {
seal.fulfill(())
}.catch(on: LokiAPI.errorHandlingQueue) { [weak self] error in
print("[Loki] Polling \(nextSnode) failed; dropping it and switching to next snode.")
LokiAPI.dropIfNeeded(nextSnode, hexEncodedPublicKey: userHexEncodedPublicKey)
if let error = error as? Error, error == .pollLimitReached {
self?.pollCount = 0
} else {
print("[Loki] Polling \(nextSnode) failed; dropping it and switching to next snode.")
LokiAPI.dropIfNeeded(nextSnode, hexEncodedPublicKey: userHexEncodedPublicKey)
}
self?.pollNextSnode(seal: seal)
}
} else {
@ -77,7 +98,12 @@ public final class LokiPoller : NSObject {
guard let strongSelf = self, !strongSelf.hasStopped else { return Promise { $0.fulfill(()) } }
let messages = LokiAPI.parseRawMessagesResponse(rawResponse, from: target)
strongSelf.onMessagesReceived(messages)
return strongSelf.poll(target, seal: longTermSeal)
strongSelf.pollCount += 1
if strongSelf.pollCount == LokiPoller.maxPollCount {
throw Error.pollLimitReached
} else {
return strongSelf.poll(target, seal: longTermSeal)
}
}
}
}

@ -12,7 +12,7 @@ public final class LokiPublicChatPoller : NSObject {
// MARK: Settings
private let pollForNewMessagesInterval: TimeInterval = 4
private let pollForDeletedMessagesInterval: TimeInterval = 20
private let pollForDeletedMessagesInterval: TimeInterval = 60
private let pollForModeratorsInterval: TimeInterval = 10 * 60
private let pollForDisplayNamesInterval: TimeInterval = 60

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

@ -22,5 +22,6 @@
#pragma mark Settings
- (BOOL)shouldBeSaved { return NO; }
- (uint)ttl { return (uint)[LKTTLUtilities getTTLFor:LKMessageTypeSessionRequest]; }
- (BOOL)shouldSyncTranscript { return NO; }
@end

@ -90,10 +90,10 @@ public final class FriendRequestProtocol : NSObject {
public static func acceptFriendRequest(from hexEncodedPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) {
// Accept all outstanding friend requests associated with this user and try to establish sessions with the
// subset of their devices that haven't sent a friend request.
guard ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) else {
assertionFailure("Invalid session ID \(hexEncodedPublicKey)")
return;
}
guard ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) else {
assertionFailure("Invalid session ID \(hexEncodedPublicKey)")
return;
}
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: hexEncodedPublicKey, in: transaction)
for device in linkedDevices {
@ -105,10 +105,10 @@ public final class FriendRequestProtocol : NSObject {
// We sent a friend request to this device before, how can we be sure that it hasn't expired?
} else if friendRequestStatus == .none || friendRequestStatus == .requestExpired {
// TODO: Need to track these so that we can expire them and resend incase the other user wasn't online after we sent
let autoGeneratedFRMessageSend = MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessageSend(for: device, in: transaction)
OWSDispatch.sendingQueue().async {
let messageSender = SSKEnvironment.shared.messageSender
messageSender.sendMessage(autoGeneratedFRMessageSend)
MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessageSend(for: thread.contactIdentifier(), in: transaction) // NOT hexEncodedPublicKey
.done(on: OWSDispatch.sendingQueue()) { autoGeneratedFRMessageSend in
let messageSender = SSKEnvironment.shared.messageSender
messageSender.sendMessage(autoGeneratedFRMessageSend)
}
}
}

@ -9,7 +9,7 @@ public final class MentionsManager : NSObject {
set { LokiAPI.stateQueue.sync { _userHexEncodedPublicKeyCache = newValue } }
}
// TODO: I don't think this stateQueue stuff actually helps avoid race conditions
// TODO: I don't think stateQueue actually helps avoid race conditions
internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() }
@ -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)
}
}

@ -9,8 +9,8 @@ import PromiseKit
// Document the expected cases for everything.
// Express those cases in tests.
@objc(LKSessionProtocol)
public final class SessionProtocol : NSObject {
@objc(LKSessionMetaProtocol)
public final class SessionMetaProtocol : NSObject {
internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() }
@ -48,7 +48,7 @@ public final class SessionProtocol : NSObject {
if let openGroup = LokiDatabaseUtilities.getPublicChat(for: thread.uniqueId!, in: transaction) {
result = [ openGroup.server ] // Aim the message at the open group server
} else {
// TODO: Handle
// TODO: Handle?
}
}
} else {
@ -58,14 +58,6 @@ public final class SessionProtocol : NSObject {
}
// MARK: Note to Self
// BEHAVIOR NOTE: OWSMessageSender.sendMessageToService:senderCertificate:success:failure: aborts early and just sends
// a sync message instead if the message it's supposed to send is considered a note to self (INCLUDING linked devices).
// BEHAVIOR NOTE: OWSMessageSender.sendMessage: aborts early and does nothing if the message is target at
// the current user (EXCLUDING linked devices).
// BEHAVIOR NOTE: OWSMessageSender.handleMessageSentLocally:success:failure: doesn't send a sync transcript if the message
// that was sent is considered a note to self (INCLUDING linked devices) but it does then mark the message as read.
// TODO: Check that the behaviors described above make sense
@objc(isThreadNoteToSelf:)
public static func isThreadNoteToSelf(_ thread: TSThread) -> Bool {
@ -77,23 +69,12 @@ public final class SessionProtocol : NSObject {
return isNoteToSelf
}
@objc(isMessageNoteToSelf:inThread:)
public static func isMessageNoteToSelf(_ message: TSOutgoingMessage, in thread: TSThread) -> Bool {
guard let thread = thread as? TSContactThread, !(message is OWSOutgoingSyncMessage) && !(message is DeviceLinkMessage) else { return false }
var isNoteToSelf = false
storage.dbReadConnection.read { transaction in
isNoteToSelf = LokiDatabaseUtilities.isUserLinkedDevice(thread.contactIdentifier(), transaction: transaction)
}
return isNoteToSelf
}
// MARK: Transcripts
@objc(shouldSendTranscriptForMessage:in:)
public static func shouldSendTranscript(for message: TSOutgoingMessage, in thread: TSThread) -> Bool {
let isNoteToSelf = isMessageNoteToSelf(message, in: thread)
let isOpenGroupMessage = (thread as? TSGroupThread)?.isPublicChat == true
let wouldSignalRequireTranscript = (AreRecipientUpdatesEnabled() || !message.hasSyncedTranscript)
return wouldSignalRequireTranscript && !isNoteToSelf && !isOpenGroupMessage && !(message is DeviceLinkMessage)
return wouldSignalRequireTranscript && !isOpenGroupMessage
}
// MARK: Typing Indicators
@ -101,21 +82,17 @@ public final class SessionProtocol : NSObject {
/// send them if certain conditions are met.
@objc(shouldSendTypingIndicatorForThread:)
public static func shouldSendTypingIndicator(for thread: TSThread) -> Bool {
return !thread.isGroupThread() && !isThreadNoteToSelf(thread)
}
guard !thread.isGroupThread(), let contact = thread.contactIdentifier() else { return false }
// MARK: Receipts
// Used from OWSReadReceiptManager
@objc(shouldSendReadReceiptForThread:)
public static func shouldSendReadReceipt(for thread: TSThread) -> Bool {
return !isThreadNoteToSelf(thread) && !thread.isGroupThread()
}
var isFriend = false;
storage.dbReadConnection.read { transaction in
isFriend = storage.getFriendRequestStatus(for: contact, transaction: transaction) == .friends
}
// TODO: Not sure how these two relate. EDIT: I think the one below is used to block delivery receipts. That means that
// right now we do send delivery receipts in note to self, but not read receipts. Other than that their behavior should
// be identical. Should we just not send any kind of receipt in note to self?
return isFriend
}
// Used from OWSOutgoingReceiptManager
// MARK: Receipts
@objc(shouldSendReceiptForThread:)
public static func shouldSendReceipt(for thread: TSThread) -> Bool {
guard !thread.isGroupThread(), let contact = thread.contactIdentifier() else { return false }
@ -129,25 +106,11 @@ public final class SessionProtocol : NSObject {
}
// MARK: - Receiving
// When a message comes in, OWSMessageManager does things in this order:
// 1. Checks if the message is a friend request from before restoration and ignores it if so
// 2. Handles friend request acceptance if needed
// 3. Checks if the message is a duplicate sync message and ignores it if so
// 4. Handles pre keys if needed (this also might trigger a session reset)
// 5. Updates P2P info if the message is a P2P address message
// 6. Handle device linking requests or authorizations if needed (it now doesn't continue along the normal message handling path)
// - If the message is a data message and has the session request flag set, processing stops here
// - If the message is a data message and has the session restore flag set, processing stops here
// 7. If the message got to this point, and it has an updated profile key attached, it'll now handle the profile key
// - If the message is a closed group message, it'll now check if it needs to be ignored
// ...
// MARK: - Decryption
@objc(shouldSkipMessageDecryptResult:)
public static func shouldSkipMessageDecryptResult(_ result: OWSMessageDecryptResult) -> Bool {
// Called from OWSMessageReceiver to prevent messages from even being added to the processing queue for some reason
return result.source == getUserHexEncodedPublicKey() // NOTE: This doesn't take into account multi device
// Called from OWSMessageReceiver to prevent messages from even being added to the processing queue
// TODO: Why is this function needed at all?
return result.source == getUserHexEncodedPublicKey() // This intentionally doesn't take into account multi device
}
// MARK: Profile Updating
@ -155,12 +118,14 @@ public final class SessionProtocol : NSObject {
public static func updateDisplayNameIfNeeded(for hexEncodedPublicKey: String, using dataMessage: SSKProtoDataMessage, appendingShortID appendShortID: Bool, in transaction: YapDatabaseReadWriteTransaction) {
guard let profile = dataMessage.profile, let rawDisplayName = profile.displayName else { return }
let displayName: String
// TODO: Figure out why we sometimes don't append the short ID
if appendShortID {
let shortID = hexEncodedPublicKey.substring(from: hexEncodedPublicKey.index(hexEncodedPublicKey.endIndex, offsetBy: -8))
displayName = "\(rawDisplayName) (...\(shortID))"
} else {
displayName = rawDisplayName
}
guard !displayName.isEmpty else { return }
let profileManager = SSKEnvironment.shared.profileManager
profileManager.updateProfileForContact(withID: hexEncodedPublicKey, displayName: displayName, with: transaction)
}
@ -174,7 +139,7 @@ public final class SessionProtocol : NSObject {
return
}
let profileManager = SSKEnvironment.shared.profileManager
// This dispatches async on the main queue internally, where it starts a new write transaction. Apparently that's an okay thing to do in this case?
// This dispatches async on the main queue internally where it starts a new write transaction
profileManager.setProfileKeyData(profileKey, forRecipientId: hexEncodedPublicKey, avatarURL: profilePictureURL)
}

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

@ -2,7 +2,7 @@
NS_ASSUME_NONNULL_BEGIN
/// TODO: This is just an ephemeral message with a flag set. Not sure if it needs to be its own type.
// TODO: This is just an ephemeral message with a flag set. Not sure if it needs to be its own type.
NS_SWIFT_NAME(UnlinkDeviceMessage)
@interface LKUnlinkDeviceMessage : TSOutgoingMessage

@ -19,7 +19,7 @@ public final class MultiDeviceProtocol : NSObject {
set { LokiAPI.stateQueue.sync { _lastDeviceLinkUpdate = newValue } }
}
// TODO: I don't think this stateQueue stuff actually helps avoid race conditions
// TODO: I don't think stateQueue actually helps avoid race conditions
internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() }
@ -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,110 @@ 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 copy(_ messageSend: OWSMessageSend, for destination: MultiDeviceDestination, with seal: Resolver<Void>) -> OWSMessageSend {
var recipient: SignalRecipient!
storage.dbReadConnection.read { transaction in
recipient = SignalRecipient.getOrBuildUnsavedRecipient(forRecipientId: destination.hexEncodedPublicKey, transaction: transaction)
}
// TODO: Why is it okay that the thread, sender certificate, etc. don't get changed?
return OWSMessageSend(message: messageSend.message, thread: messageSend.thread, recipient: recipient,
senderCertificate: messageSend.senderCertificate, udAccess: messageSend.udAccess, localNumber: messageSend.localNumber, success: {
seal.fulfill(())
}, failure: { error in
seal.reject(error)
})
}
private static func sendMessage(_ messageSend: OWSMessageSend, to destination: MultiDeviceDestination, in transaction: YapDatabaseReadTransaction) -> Promise<Void> {
let (threadPromise, threadPromiseSeal) = Promise<TSContactThread>.pending()
if let thread = TSContactThread.getWithContactId(destination.hexEncodedPublicKey, transaction: transaction) {
threadPromiseSeal.fulfill(thread)
} else {
// Dispatch async on the main queue to avoid nested write transactions
DispatchQueue.main.async {
storage.dbReadWriteConnection.readWrite { transaction in
let thread = TSContactThread.getOrCreateThread(withContactId: destination.hexEncodedPublicKey, transaction: transaction)
threadPromiseSeal.fulfill(thread)
}
}
}
return threadPromise.then(on: OWSDispatch.sendingQueue()) { thread -> Promise<Void> in
let message = messageSend.message
let messageSender = SSKEnvironment.shared.messageSender
let (promise, seal) = Promise<Void>.pending()
let shouldSendAutoGeneratedFR = !thread.isContactFriend && !(message is FriendRequestMessage)
&& message.shouldBeSaved() // shouldBeSaved indicates it isn't a transient message
if !shouldSendAutoGeneratedFR {
let messageSendCopy = copy(messageSend, for: destination, with: seal)
messageSender.sendMessage(messageSendCopy)
} else {
// Dispatch async on the main queue to avoid nested write transactions
DispatchQueue.main.async {
storage.dbReadWriteConnection.readWrite { transaction in
getAutoGeneratedMultiDeviceFRMessageSend(for: destination.hexEncodedPublicKey, in: transaction, seal: seal)
.done(on: OWSDispatch.sendingQueue()) { autoGeneratedFRMessageSend in
messageSender.sendMessage(autoGeneratedFRMessageSend)
}
}
}
}
return promise
}
}
@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)
var promises: [Promise<Void>] = []
let masterDestination = destinations.first { $0.isMaster }
if let masterDestination = masterDestination {
storage.dbReadConnection.read { transaction in
promises.append(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)
let slaveDestinations = destinations.filter { !$0.isMaster }
slaveDestinations.forEach { slaveDestination in
storage.dbReadConnection.read { transaction in
promises.append(sendMessage(messageSend, to: slaveDestination, in: transaction))
}
}
when(resolved: promises).done(on: OWSDispatch.sendingQueue()) { results in
let errors = results.compactMap { result -> Error? in
if case Result.rejected(let error) = result {
return error
} else {
return nil
}
messageSender.sendMessage(frMessageSend)
}
if errors.isEmpty {
messageSend.success()
} else {
messageSend.failure(errors.first!)
}
}
}.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)
}
@ -120,21 +163,33 @@ 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, seal externalSeal: Resolver<Void>? = nil) -> Promise<OWSMessageSend> {
// We don't update friend request status here as it is done in OWSMessageSender.sendMessage(:)
let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
let message = getAutoGeneratedMultiDeviceFRMessage(for: hexEncodedPublicKey, in: 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()
// Dispatch async on the main queue to avoid nested write transactions
DispatchQueue.main.async {
var recipientUDAccess: OWSUDAccess?
if let senderCertificate = senderCertificate {
recipientUDAccess = udManager.udAccess(forRecipientId: hexEncodedPublicKey, requireSyncAccess: true) // Starts a new write transaction internally
}
let messageSend = OWSMessageSend(message: message, thread: thread, recipient: recipient, senderCertificate: senderCertificate,
udAccess: recipientUDAccess, localNumber: getUserHexEncodedPublicKey(), success: {
externalSeal?.fulfill(())
}, failure: { error in
externalSeal?.reject(error)
})
seal.fulfill(messageSend)
}
return OWSMessageSend(message: message, thread: thread, recipient: recipient, senderCertificate: senderCertificate,
udAccess: recipientUDAccess, localNumber: getUserHexEncodedPublicKey(), success: {
}, failure: { _ in
})
return promise
}
// MARK: - Receiving
@ -157,8 +212,8 @@ public final class MultiDeviceProtocol : NSObject {
}
// Set any profile info (the device link authorization also includes the master device's profile info)
if let dataMessage = protoContent.dataMessage {
SessionProtocol.updateDisplayNameIfNeeded(for: master, using: dataMessage, appendingShortID: false, in: transaction)
SessionProtocol.updateProfileKeyIfNeeded(for: master, using: dataMessage)
SessionMetaProtocol.updateDisplayNameIfNeeded(for: master, using: dataMessage, appendingShortID: false, in: transaction)
SessionMetaProtocol.updateProfileKeyIfNeeded(for: master, using: dataMessage)
}
} else { // Request
print("[Loki] Received a device link request from: \(hexEncodedPublicKey).") // Intentionally not `slave`
@ -187,7 +242,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)
@ -200,17 +255,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)
}
@ -223,7 +277,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

@ -2,7 +2,7 @@
NS_ASSUME_NONNULL_BEGIN
/// TODO: This is just a friend request message with a flag set. Not sure if it needs to be its own type.
// TODO: This is just a friend request message with a flag set. Not sure if it needs to be its own type.
NS_SWIFT_NAME(SessionRestoreMessage)
@interface LKSessionRestoreMessage : LKFriendRequestMessage

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

@ -80,7 +80,7 @@ public final class SessionManagementProtocol : NSObject {
}
// MARK: - Sending
// TODO: Confusing that we have this but also the receiving version
// TODO: Confusing that we have this but also a receiving version
@objc(sending_startSessionResetInThread:using:)
public static func sending_startSessionReset(in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
guard let thread = thread as? TSContactThread else {
@ -91,8 +91,7 @@ public final class SessionManagementProtocol : NSObject {
let devices = thread.sessionRestoreDevices // TODO: Rename this
for device in devices {
guard device.count != 0 else { continue }
let sessionResetMessageSend = getSessionResetMessageSend(for: device, in: transaction)
OWSDispatch.sendingQueue().async {
getSessionResetMessageSend(for: device, in: transaction).done(on: OWSDispatch.sendingQueue()) { sessionResetMessageSend in
messageSender.sendMessage(sessionResetMessageSend)
}
}
@ -112,7 +111,11 @@ public final class SessionManagementProtocol : NSObject {
}
@objc(getSessionResetMessageSendForHexEncodedPublicKey:in:)
public static func getSessionResetMessageSend(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> OWSMessageSend {
public static func objc_getSessionResetMessageSend(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> AnyPromise {
return AnyPromise.from(getSessionResetMessageSend(for: hexEncodedPublicKey, in: transaction))
}
public static func getSessionResetMessageSend(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> Promise<OWSMessageSend> {
let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction)
let isSlaveDeviceThread = masterHexEncodedPublicKey != hexEncodedPublicKey
@ -122,16 +125,22 @@ public final class SessionManagementProtocol : NSObject {
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)
}
return OWSMessageSend(message: message, thread: thread, recipient: recipient, senderCertificate: senderCertificate,
udAccess: recipientUDAccess, localNumber: getUserHexEncodedPublicKey(), success: {
let (promise, seal) = Promise<OWSMessageSend>.pending()
// Dispatch async on the main queue to avoid nested write transactions
DispatchQueue.main.async {
var recipientUDAccess: OWSUDAccess?
if let senderCertificate = senderCertificate {
recipientUDAccess = udManager.udAccess(forRecipientId: hexEncodedPublicKey, requireSyncAccess: true) // Starts a new write transaction internally
}
let messageSend = OWSMessageSend(message: message, thread: thread, recipient: recipient, senderCertificate: senderCertificate,
udAccess: recipientUDAccess, localNumber: getUserHexEncodedPublicKey(), success: {
}, failure: { error in
}, failure: { error in
})
})
seal.fulfill(messageSend)
}
return promise
}
// MARK: - Receiving
@ -147,7 +156,6 @@ public final class SessionManagementProtocol : NSObject {
thread.addSessionRestoreDevice(hexEncodedPublicKey, transaction: transaction)
default: break
}
}
@objc(isSessionRestoreMessage:)
@ -156,14 +164,13 @@ public final class SessionManagementProtocol : NSObject {
return dataMessage.flags & UInt32(sessionRestoreFlag.rawValue) != 0
}
// TODO: Is this only ever used for closed groups?
@objc(isSessionRequestMessage:)
public static func isSessionRequestMessage(_ dataMessage: SSKProtoDataMessage) -> Bool {
let sessionRequestFlag = SSKProtoDataMessage.SSKProtoDataMessageFlags.sessionRequest
return dataMessage.flags & UInt32(sessionRequestFlag.rawValue) != 0
}
// TODO: This seriously needs some explanation of when we expect pre key bundles to be attached
// TODO: This needs an explanation of when we expect pre key bundles to be attached
@objc(handlePreKeyBundleMessageIfNeeded:wrappedIn:using:)
public static func handlePreKeyBundleMessageIfNeeded(_ protoContent: SSKProtoContent, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) {
// The envelope source is set during UD decryption
@ -175,11 +182,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: Should this 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
@ -187,7 +194,7 @@ public final class SessionManagementProtocol : NSObject {
}
}
// TODO: Confusing that we have this but also the sending version
// TODO: Confusing that we have this but also a sending version
@objc(receiving_startSessionResetInThread:using:)
public static func receiving_startSessionReset(in thread: TSContactThread, using transaction: YapDatabaseReadWriteTransaction) {
let hexEncodedPublicKey = thread.contactIdentifier()

@ -44,7 +44,7 @@ public final class SyncMessagesProtocol : NSObject {
guard thread.isContactFriend && thread.shouldThreadBeVisible && !thread.isForceHidden else { return }
friends.append(SignalAccount(recipientId: hexEncodedPublicKey))
}
friends.append(SignalAccount(recipientId: getUserHexEncodedPublicKey())) // TODO: We sure about this?
friends.append(SignalAccount(recipientId: getUserHexEncodedPublicKey())) // TODO: Are we sure about this?
let syncManager = SSKEnvironment.shared.syncManager
let promises = friends.chunked(by: 3).map { friends -> Promise<Void> in // TODO: Does this always fit?
return Promise(syncManager.syncContacts(for: friends)).map { _ in }
@ -117,8 +117,8 @@ public final class SyncMessagesProtocol : NSObject {
guard let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: getUserHexEncodedPublicKey(), in: transaction) else { return }
let wasSentByMasterDevice = (masterHexEncodedPublicKey == hexEncodedPublicKey)
guard wasSentByMasterDevice else { return }
SessionProtocol.updateDisplayNameIfNeeded(for: masterHexEncodedPublicKey, using: dataMessage, appendingShortID: false, in: transaction)
SessionProtocol.updateProfileKeyIfNeeded(for: masterHexEncodedPublicKey, using: dataMessage)
SessionMetaProtocol.updateDisplayNameIfNeeded(for: masterHexEncodedPublicKey, using: dataMessage, appendingShortID: false, in: transaction)
SessionMetaProtocol.updateProfileKeyIfNeeded(for: masterHexEncodedPublicKey, using: dataMessage)
}
@objc(handleClosedGroupUpdatedSyncMessageIfNeeded:using:)
@ -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)
@ -175,6 +175,7 @@ public final class SyncMessagesProtocol : NSObject {
// TODO: Does the function below need to handle multi device??
// We need to send a FR message here directly to the user. Multi device doesn't come into play.
let autoGeneratedFRMessage = MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessage(for: hexEncodedPublicKey, in: transaction)
thread.friendRequestStatus = .requestSending
thread.isForceHidden = true
thread.save(with: transaction)
@ -185,6 +186,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)
storage.setFriendRequestStatus(.requestSent, for: hexEncodedPublicKey, transaction: transaction)
@ -195,6 +197,7 @@ public final class SyncMessagesProtocol : NSObject {
storage.dbReadWriteConnection.readWrite { transaction in
storage.setFriendRequestStatus(friendRequestStatus, for: hexEncodedPublicKey, transaction: transaction)
autoGeneratedFRMessage.remove(with: transaction)
thread.friendRequestStatus = .none
thread.isForceHidden = false
thread.save(with: transaction)
}
@ -225,7 +228,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)
}

@ -404,10 +404,21 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
BOOL duplicateEnvelope = [self.incomingMessageFinder existsMessageWithTimestamp:envelope.timestamp
sourceId:envelope.source
sourceDeviceId:envelope.sourceDevice
transaction:transaction];
OWSPrimaryStorage *storage = OWSPrimaryStorage.sharedManager;
__block NSSet<NSString *> *linkedDeviceHexEncodedPublicKeys;
[storage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
linkedDeviceHexEncodedPublicKeys = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:envelope.source in:transaction];
}];
BOOL duplicateEnvelope = NO;
for (NSString *hexEncodedPublicKey in linkedDeviceHexEncodedPublicKeys) {
duplicateEnvelope = duplicateEnvelope
|| [self.incomingMessageFinder existsMessageWithTimestamp:envelope.timestamp
sourceId:hexEncodedPublicKey
sourceDeviceId:envelope.sourceDevice
transaction:transaction];
}
if (duplicateEnvelope) {
OWSLogInfo(@"Ignoring previously received envelope from: %@ with timestamp: %llu.",
envelopeAddress(envelope),
@ -441,7 +452,7 @@ NS_ASSUME_NONNULL_BEGIN
// Loki: Handle address message if needed
/*
[LKSessionProtocol handleP2PAddressMessageIfNeeded:contentProto wrappedIn:envelope];
[LKSessionMetaProtocol handleP2PAddressMessageIfNeeded:contentProto wrappedIn:envelope];
*/
// Loki: Handle device linking message if needed
@ -1250,8 +1261,9 @@ NS_ASSUME_NONNULL_BEGIN
}
// Loki: Update device links in a blocking way
// TODO: This is pretty bad for performance...
// The envelope source is set during UD decryption.
// FIXME: This is horrible for performance
// FIXME: ========
// The envelope source is set during UD decryption
if ([ECKeyPair isValidHexEncodedPublicKeyWithCandidate:envelope.source] && dataMessage.publicChatInfo == nil) { // Handled in LokiPublicChatPoller for open group messages
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[[LKMultiDeviceProtocol updateDeviceLinksIfNeededForHexEncodedPublicKey:envelope.source in:transaction].ensureOn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^() {
@ -1259,8 +1271,9 @@ NS_ASSUME_NONNULL_BEGIN
}).catchOn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(NSError *error) {
dispatch_semaphore_signal(semaphore);
}) retainUntilComplete];
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC));
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 4 * NSEC_PER_SEC));
}
// FIXME: ========
if (groupId.length > 0) {
NSMutableSet *newMemberIds = [NSMutableSet setWithArray:dataMessage.group.members];
@ -1296,10 +1309,10 @@ NS_ASSUME_NONNULL_BEGIN
}
// Loki: Handle profile key update if needed
[LKSessionProtocol updateProfileKeyIfNeededForHexEncodedPublicKey:senderMasterHexEncodedPublicKey using:dataMessage];
[LKSessionMetaProtocol updateProfileKeyIfNeededForHexEncodedPublicKey:senderMasterHexEncodedPublicKey using:dataMessage];
// Loki: Handle display name update if needed
[LKSessionProtocol updateDisplayNameIfNeededForHexEncodedPublicKey:senderMasterHexEncodedPublicKey using:dataMessage appendingShortID:NO in:transaction];
[LKSessionMetaProtocol updateDisplayNameIfNeededForHexEncodedPublicKey:senderMasterHexEncodedPublicKey using:dataMessage appendingShortID:NO in:transaction];
switch (dataMessage.group.type) {
case SSKProtoGroupContextTypeUpdate: {
@ -1326,7 +1339,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
@ -1520,8 +1533,8 @@ NS_ASSUME_NONNULL_BEGIN
wasReceivedByUD:wasReceivedByUD];
// TODO: Are we sure this works correctly with multi device?
[LKSessionProtocol updateDisplayNameIfNeededForHexEncodedPublicKey:incomingMessage.authorId using:dataMessage appendingShortID:YES in:transaction];
[LKSessionProtocol updateProfileKeyIfNeededForHexEncodedPublicKey:thread.contactIdentifier using:dataMessage];
[LKSessionMetaProtocol updateDisplayNameIfNeededForHexEncodedPublicKey:incomingMessage.authorId using:dataMessage appendingShortID:YES in:transaction];
[LKSessionMetaProtocol updateProfileKeyIfNeededForHexEncodedPublicKey:thread.contactIdentifier using:dataMessage];
// Loki: Parse Loki specific properties if needed
/*

@ -398,7 +398,7 @@ NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessin
OWSAssertDebug(transaction);
// Loki: Don't process any messages from ourself
if ([LKSessionProtocol shouldSkipMessageDecryptResult:result]) {
if ([LKSessionMetaProtocol shouldSkipMessageDecryptResult:result]) {
dispatch_async(self.serialQueue, ^{
completion(YES);
});

@ -93,14 +93,4 @@ public class OWSMessageSend: NSObject {
// We "fail over" to non-UD sends after auth errors sending via UD.
disableUD()
}
public func copy(with destination: MultiDeviceProtocol.MultiDeviceDestination) -> OWSMessageSend {
var recipient: SignalRecipient!
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 }
return OWSMessageSend(message: message, thread: thread, recipient: recipient, senderCertificate: senderCertificate, udAccess: udAccess, localNumber: localNumber, success: success, failure: failure)
}
}

@ -360,7 +360,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
OWSAssertDebug([message.body lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= kOversizeTextMessageSizeThreshold);
}
if (!message.thread.isGroupThread) {
if (!message.thread.isGroupThread && ![LKSessionMetaProtocol isMessageNoteToSelf:message.thread]) {
// Not really true but better from a UI point of view
[NSNotificationCenter.defaultCenter postNotificationName:NSNotification.calculatingPoW object:[[NSNumber alloc] initWithUnsignedLongLong:message.timestamp]];
}
@ -500,10 +500,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
__block NSMutableSet<NSString *> *recipientIds = [NSMutableSet new];
if ([message isKindOfClass:[OWSOutgoingSyncMessage class]]) {
recipientIds = [LKSessionProtocol getDestinationsForOutgoingSyncMessage];
recipientIds = [LKSessionMetaProtocol getDestinationsForOutgoingSyncMessage];
} else if (thread.isGroupThread) {
TSGroupThread *groupThread = (TSGroupThread *)thread;
recipientIds = [LKSessionProtocol getDestinationsForOutgoingGroupMessage:message inThread:thread];
recipientIds = [LKSessionMetaProtocol getDestinationsForOutgoingGroupMessage:message inThread:thread];
} else if ([thread isKindOfClass:[TSContactThread class]]) {
NSString *recipientContactId = ((TSContactThread *)thread).contactIdentifier;
@ -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];
}
@ -668,10 +670,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
return failureHandler(error);
}
// Loki: Abort early and send a sync transcript if this is a note to self
if ([LKSessionProtocol isMessageNoteToSelf:message inThread:thread]) {
// FIXME: I think this is where the duplicate sync messages might be coming from. Signal just invokes successHandler() here.
[self sendSyncTranscriptForMessage:message isRecipientUpdate:NO success:^{ } failure:^(NSError *error) { }];
// In the "self-send" special case, we ony need to send a sync message with a delivery receipt
// Loki: Take into account multi device
if ([LKSessionMetaProtocol isMessageNoteToSelf:thread] && !([message isKindOfClass:LKDeviceLinkMessage.class])) {
// Don't mark self-sent messages as read (or sent) until the sync transcript is sent
successHandler();
return;
}
@ -914,14 +916,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
TSOutgoingMessage *message = messageSend.message;
SignalRecipient *recipient = messageSend.recipient;
// Loki: Ignore messages addressed to self
// TODO: Why?
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
if ([messageSend.recipient.recipientId isEqual:userHexEncodedPublicKey]) {
[LKLogger print:[NSString stringWithFormat:@"[Loki] Ignoring %@ addressed to self.", message.class]];
return messageSend.success();
}
OWSLogInfo(@"Attempting to send message: %@, timestamp: %llu, recipient: %@.",
message.class,
@ -1191,7 +1185,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
[message saveIsCalculatingProofOfWork:YES withTransaction:transaction];
// Update the message and thread if needed
if (signalMessage.isFriendRequest) {
[message.thread saveFriendRequestStatus:LKThreadFriendRequestStatusRequestSending withTransaction:transaction];
TSContactThread *thread = [TSContactThread getThreadWithContactId:recipientID transaction:transaction]; // Take into account multi device
[thread saveFriendRequestStatus:LKThreadFriendRequestStatusRequestSending withTransaction:transaction];
[message saveFriendRequestStatus:LKMessageFriendRequestStatusSendingOrFailed withTransaction:transaction];
}
}];
@ -1203,7 +1198,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
// Update the message and thread if needed
if (signalMessage.isFriendRequest) {
[message.thread saveFriendRequestStatus:LKThreadFriendRequestStatusNone withTransaction:transaction];
TSContactThread *thread = [TSContactThread getThreadWithContactId:recipientID transaction:transaction]; // Take into account multi device
[thread saveFriendRequestStatus:LKThreadFriendRequestStatusNone withTransaction:transaction];
[message saveFriendRequestStatus:LKMessageFriendRequestStatusSendingOrFailed withTransaction:transaction];
}
// Update the PoW calculation status
@ -1230,7 +1226,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
if (!message.skipSave) {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
// Update the thread
[message.thread saveFriendRequestStatus:LKThreadFriendRequestStatusRequestSent withTransaction:transaction];
TSContactThread *thread = [TSContactThread getThreadWithContactId:recipientID transaction:transaction]; // Take into account multi device
[thread saveFriendRequestStatus:LKThreadFriendRequestStatusRequestSent withTransaction:transaction];
[message.thread removeOldOutgoingFriendRequestMessagesIfNeededWithTransaction:transaction];
if ([message.thread isKindOfClass:[TSContactThread class]]) {
[((TSContactThread *) message.thread) removeAllSessionRestoreDevicesWithTransaction:transaction];
@ -1486,9 +1483,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
failure:(RetryableFailureHandler)failure
{
dispatch_block_t success = ^{
// Loki: Handle note to self case
BOOL isNoteToSelf = [LKSessionProtocol isMessageNoteToSelf:message inThread:message.thread];
if (isNoteToSelf) {
// Don't mark self-sent messages as read (or sent) until the sync transcript is sent
// Loki: Take into account multi device
BOOL isNoteToSelf = [LKSessionMetaProtocol isMessageNoteToSelf:message.thread];
if (isNoteToSelf && !([message isKindOfClass:LKDeviceLinkMessage.class])) {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *recipientId in message.sendingRecipientIds) {
[message updateWithReadRecipientId:recipientId readTimestamp:message.timestamp transaction:transaction];
@ -1509,7 +1507,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
return success();
}
BOOL shouldSendTranscript = [LKSessionProtocol shouldSendTranscriptForMessage:message in:message.thread];
BOOL shouldSendTranscript = [LKSessionMetaProtocol shouldSendTranscriptForMessage:message in:message.thread];
if (!shouldSendTranscript) {
return success();
}
@ -1565,9 +1563,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];
}

@ -174,7 +174,7 @@ NSString *const kOutgoingReadReceiptManagerCollection = @"kOutgoingReadReceiptMa
TSThread *thread = [TSContactThread getOrCreateThreadWithContactId:recipientId];
if (![LKSessionProtocol shouldSendReceiptForThread:thread]) {
if (![LKSessionMetaProtocol shouldSendReceiptForThread:thread]) {
continue;
}

@ -286,7 +286,7 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
self.toLinkedDevicesReadReceiptMap[threadUniqueId] = newReadReceipt;
}
if (![LKSessionProtocol shouldSendReadReceiptForThread:message.thread]) {
if (![LKSessionMetaProtocol shouldSendReceiptForThread:message.thread]) {
return;
}

@ -323,7 +323,7 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
return
}
if !SessionProtocol.shouldSendTypingIndicator(for: thread) { return }
if !SessionMetaProtocol.shouldSendTypingIndicator(for: thread) { return }
let message = TypingIndicatorMessage(thread: thread, action: action)
messageSender.sendPromise(message: message).retainUntilComplete()

Loading…
Cancel
Save