From a26ee12f8d57c3c535496f45cb83bdea3e377cc9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 1 Mar 2022 14:06:37 +1100 Subject: [PATCH] Further work on Id Blinding Renamed the setter for the SOGS 'Server' object for consistency Updated the Curve25519Kit repo to use an Oxen fork Updated the MockDataGenerator to accomodate the latest changes Updated the ConversationVC to better support getting replaced when the conversion from blinded to unblinded happens while on that screen Added a cache for the mapping between blinded ids and standard ids (gets cached whenever a valid match is found) Added a migration to remove the old 'authToken, 'lastMessageServerId' and 'lastDeletionServerId' collections (redundant in SOGS V4) --- Podfile | 3 +- Podfile.lock | 12 +- Session.xcodeproj/project.pbxproj | 8 ++ .../ConversationVC+Interaction.swift | 78 +++++++++++ Session/Conversations/ConversationVC.swift | 106 +++++++++++++- .../Conversations/Input View/InputView.swift | 11 ++ .../Message Cells/MessageCell.swift | 2 +- Session/Utilities/ContactUtilities.swift | 42 ++++-- Session/Utilities/MockDataGenerator.swift | 35 ++++- .../Contacts/BlindedIdMapping.swift | 40 ++++++ .../Database/Storage+Contacts.swift | 40 ++++++ .../Database/Storage+Messaging.swift | 59 ++++---- .../Database/Storage+OpenGroups.swift | 79 +++-------- .../Messages/Signal/TSInteraction.h | 4 + .../Messages/Signal/TSInteraction.m | 6 + .../Open Groups/Models/Capabilities.swift | 7 + .../Open Groups/OpenGroupManager.swift | 3 +- .../MessageReceiver+Handling.swift | 132 ++++++++++++++++-- SessionMessagingKit/Storage.swift | 2 +- .../Threads/Notification+Thread.swift | 6 + SessionMessagingKit/Threads/TSContactThread.h | 11 ++ SessionMessagingKit/Threads/TSContactThread.m | 29 ++++ .../_TestUtilities/TestStorage.swift | 2 +- .../Database/Migrations/SOGSV4Migration.swift | 33 +++++ 24 files changed, 618 insertions(+), 132 deletions(-) create mode 100644 SessionMessagingKit/Contacts/BlindedIdMapping.swift create mode 100644 SignalUtilitiesKit/Database/Migrations/SOGSV4Migration.swift diff --git a/Podfile b/Podfile index a6b96cb29..c903f9d7d 100644 --- a/Podfile +++ b/Podfile @@ -24,8 +24,7 @@ abstract_target 'GlobalDependencies' do # Dependencies to be included only in all extensions/frameworks abstract_target 'FrameworkAndExtensionDependencies' do - # TODO: Swap this to use an oxen-io fork - pod 'Curve25519Kit', git: 'https://github.com/mpretty-cyro/session-ios-curve-25519-kit.git', branch: 'session' + pod 'Curve25519Kit', git: 'https://github.com/oxen-io/session-ios-curve-25519-kit.git', branch: 'session-version' pod 'SignalCoreKit', git: 'https://github.com/oxen-io/session-ios-core-kit', branch: 'session-version' target 'SessionNotificationServiceExtension' diff --git a/Podfile.lock b/Podfile.lock index 17abfa0e7..f504ecaa4 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -123,7 +123,7 @@ PODS: DEPENDENCIES: - AFNetworking - CryptoSwift - - Curve25519Kit (from `https://github.com/mpretty-cyro/session-ios-curve-25519-kit.git`, branch `session`) + - Curve25519Kit (from `https://github.com/oxen-io/session-ios-curve-25519-kit.git`, branch `session-version`) - Mantle (from `https://github.com/signalapp/Mantle`, branch `signal-master`) - Nimble - NVActivityIndicatorView @@ -156,8 +156,8 @@ SPEC REPOS: EXTERNAL SOURCES: Curve25519Kit: - :branch: session - :git: https://github.com/mpretty-cyro/session-ios-curve-25519-kit.git + :branch: session-version + :git: https://github.com/oxen-io/session-ios-curve-25519-kit.git Mantle: :branch: signal-master :git: https://github.com/signalapp/Mantle @@ -175,8 +175,8 @@ EXTERNAL SOURCES: CHECKOUT OPTIONS: Curve25519Kit: - :commit: a23049232dc6c18928cdacfbcef287dad954c5c6 - :git: https://github.com/mpretty-cyro/session-ios-curve-25519-kit.git + :commit: b79c2ace600bfd3784e9c33cf1f254b121312edc + :git: https://github.com/oxen-io/session-ios-curve-25519-kit.git Mantle: :commit: e7e46253bb01ce39525d90aa69ed9e85e758bfc4 :git: https://github.com/signalapp/Mantle @@ -214,6 +214,6 @@ SPEC CHECKSUMS: YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 918ef11baf24eac2df681cd6a3781f536f9d384a +PODFILE CHECKSUM: 2cc64d50f25c3b1627c3e958ae50e25fead25564 COCOAPODS: 1.11.2 diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index fa898d92a..64c422af3 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -771,6 +771,8 @@ F5765D284BC6ECAC0C1D33F0 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4A93ECA93B3DE800CC7D7F6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */; }; FC3BD9881A30A790005B96BB /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3BD9871A30A790005B96BB /* Social.framework */; }; FCB11D8C1A129A76002F93FB /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCB11D8B1A129A76002F93FB /* CoreMedia.framework */; }; + FD0BA51B27CD88EC00CC6805 /* BlindedIdMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0BA51A27CD88EC00CC6805 /* BlindedIdMapping.swift */; }; + FD0BA51D27CDC34600CC6805 /* SOGSV4Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0BA51C27CDC34600CC6805 /* SOGSV4Migration.swift */; }; FD5D200F27AA2B6000FEA984 /* MessageRequestResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */; }; FD5D201127AA331F00FEA984 /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */; }; FD5D201E27B0D87C00FEA984 /* SessionId.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D201D27B0D87C00FEA984 /* SessionId.swift */; }; @@ -1906,6 +1908,8 @@ F9BBF530D71905BA9007675F /* Pods-SessionShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionShareExtension/Pods-SessionShareExtension.debug.xcconfig"; sourceTree = ""; }; FC3BD9871A30A790005B96BB /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; FCB11D8B1A129A76002F93FB /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; + FD0BA51A27CD88EC00CC6805 /* BlindedIdMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlindedIdMapping.swift; sourceTree = ""; }; + FD0BA51C27CDC34600CC6805 /* SOGSV4Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSV4Migration.swift; sourceTree = ""; }; FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestResponse.swift; sourceTree = ""; }; FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = ""; }; FD5D201D27B0D87C00FEA984 /* SessionId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionId.swift; sourceTree = ""; }; @@ -2564,6 +2568,7 @@ isa = PBXGroup; children = ( B8B32020258B1A650020074B /* Contact.swift */, + FD0BA51A27CD88EC00CC6805 /* BlindedIdMapping.swift */, ); path = Contacts; sourceTree = ""; @@ -3237,6 +3242,7 @@ children = ( B8B32044258C117C0020074B /* ContactsMigration.swift */, FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */, + FD0BA51C27CDC34600CC6805 /* SOGSV4Migration.swift */, C38EF271255B6D79007E1867 /* OWSDatabaseMigration.h */, C38EF270255B6D79007E1867 /* OWSDatabaseMigration.m */, C38EF26F255B6D79007E1867 /* OWSDatabaseMigrationRunner.h */, @@ -4988,6 +4994,7 @@ C33FDC98255A582000E217F9 /* SwiftSingletons.swift in Sources */, C33FDC27255A581F00E217F9 /* YapDatabase+Promise.swift in Sources */, C33FDCD3255A582000E217F9 /* GroupUtilities.swift in Sources */, + FD0BA51D27CDC34600CC6805 /* SOGSV4Migration.swift in Sources */, FD88BADB27A750F200BBC442 /* MessageRequestsMigration.swift in Sources */, C38EF326255B6DBF007E1867 /* ConversationStyle.swift in Sources */, C38EF3B8255B6DE7007E1867 /* ImageEditorTextViewController.swift in Sources */, @@ -5117,6 +5124,7 @@ C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */, C32C5AAD256DBE8F003C73A2 /* TSInfoMessage.m in Sources */, FD5D201127AA331F00FEA984 /* ConfigurationMessage+Convenience.swift in Sources */, + FD0BA51B27CD88EC00CC6805 /* BlindedIdMapping.swift in Sources */, FDC4386927B4E6B800C60D73 /* String+Utlities.swift in Sources */, C32C5A13256DB7A5003C73A2 /* PushNotificationAPI.swift in Sources */, FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */, diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index fbd7696a0..3aa4e499d 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -2,6 +2,7 @@ import UIKit import CoreServices import Photos import PhotosUI +import Sodium import SessionUtilitiesKit import SignalUtilitiesKit @@ -813,6 +814,83 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc userDetailsSheet.modalTransitionStyle = .crossDissolve present(userDetailsSheet, animated: true, completion: nil) } + + func startThread(with sessionId: String, openGroupServer: String, openGroupPublicKey: String) { + // If the sessionId is blinded then check if there is an existing un-blinded thread with the contact + if SessionId.Prefix(from: sessionId) == .blinded { + // TODO: Ensure the above case isn't going to be an issue due to legacy messages? + // Unfortunately the whole point of id-blinding is to make it hard to reverse-engineer a standard + // sessionId, as a result in order to see if there is an unblinded contact for this blindedId we + // can only really generate blinded ids for each contact and check if any match + // + // Due to this we have made a few optimisations to try and early-out as often as possible, first + // we try to retrieve a direct cached mapping + if let mapping: BlindedIdMapping = Storage.shared.getBlindedIdMapping(with: sessionId) { + let thread: TSContactThread = TSContactThread.getOrCreateThread(contactSessionID: mapping.sessionId) + let conversationVC: ConversationVC = ConversationVC(thread: thread) + + self.navigationController?.pushViewController(conversationVC, animated: true) + return + } + + var didFindContact: Bool = false + + // Then we try loop through all approved contact threads to see if one of those contacts can be blinded to match + ContactUtilities.enumerateApprovedContactThreads { contactThread, contact, stop in + guard Sodium().sessionId(contact.sessionID, matchesBlindedId: sessionId, serverPublicKey: openGroupPublicKey) else { + return + } + + // Cache the mapping + let mapping: BlindedIdMapping = BlindedIdMapping(blindedId: sessionId, sessionId: contact.sessionID, serverPublicKey: openGroupPublicKey) + Storage.shared.cacheBlindedIdMapping(mapping) + + // Open the existing thread + let conversationVC: ConversationVC = ConversationVC(thread: contactThread) + self.navigationController?.pushViewController(conversationVC, animated: true) + + didFindContact = true + stop.pointee = true + } + + // Don't continue if we found the contact + guard !didFindContact else { return } + + // Lastly loop through existing id mappings (in case the user is looking at a different SOGS but once had + // a thread with this contact in a different SOGS and had cached the mapping) + Storage.shared.enumerateBlindedIdMapping { mapping, stop in + guard mapping.serverPublicKey != openGroupPublicKey else { return } + guard Sodium().sessionId(mapping.sessionId, matchesBlindedId: sessionId, serverPublicKey: openGroupPublicKey) else { + return + } + + // Cache the new mapping + let thread: TSContactThread = TSContactThread.getOrCreateThread(contactSessionID: mapping.sessionId) + let newMapping: BlindedIdMapping = BlindedIdMapping(blindedId: sessionId, sessionId: mapping.sessionId, serverPublicKey: openGroupPublicKey) + Storage.shared.cacheBlindedIdMapping(newMapping) + + // Open the existing thread + let conversationVC: ConversationVC = ConversationVC(thread: thread) + self.navigationController?.pushViewController(conversationVC, animated: true) + + didFindContact = true + stop.pointee = true + } + + // Don't continue if we found the contact + guard !didFindContact else { return } + } + + // Just create a new thread with the provided sessionId + let thread = TSContactThread.getOrCreateThread( + contactSessionID: sessionId, + openGroupServer: openGroupServer, + openGroupPublicKey: openGroupPublicKey + ) + let conversationVC: ConversationVC = ConversationVC(thread: thread) + + self.navigationController?.pushViewController(conversationVC, animated: true) + } // MARK: Voice Message Playback @objc func handleAudioDidFinishPlayingNotification(_ notification: Notification) { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 0e2159de4..d8d8dc079 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -1,3 +1,4 @@ +import UIKit import SessionUIKit import SessionMessagingKit @@ -13,9 +14,8 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat let focusedMessageID: String? // This is used for global search var focusedMessageIndexPath: IndexPath? var unreadViewItems: [ConversationViewItem] = [] - var scrollButtonBottomConstraint: NSLayoutConstraint? - var scrollButtonMessageRequestsBottomConstraint: NSLayoutConstraint? - var messageRequestsViewBotomConstraint: NSLayoutConstraint? + var isReplacingThread: Bool = false + // Search var isShowingSearchUI = false var lastSearchedText: String? @@ -40,7 +40,11 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat var audioSession: OWSAudioSession { Environment.shared.audioSession } var dbConnection: YapDatabaseConnection { OWSPrimaryStorage.shared().uiDatabaseConnection } var viewItems: [ConversationViewItem] { viewModel.viewState.viewItems } - override var canBecomeFirstResponder: Bool { true } + + override var canBecomeFirstResponder: Bool { + // Need to return false during the swap between threads to prevent keyboard dismissal + !isReplacingThread + } override var inputAccessoryView: UIView? { if let thread = thread as? TSGroupThread, thread.groupModel.groupType == .closedGroup && !thread.isCurrentUserMemberInGroup() { @@ -102,6 +106,10 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat private static let messageRequestButtonHeight: CGFloat = 34 + var scrollButtonBottomConstraint: NSLayoutConstraint? + var scrollButtonMessageRequestsBottomConstraint: NSLayoutConstraint? + var messageRequestsViewBotomConstraint: NSLayoutConstraint? + lazy var titleView: ConversationTitleView = { let result = ConversationTitleView(thread: thread) result.delegate = self @@ -363,6 +371,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat notificationCenter.addObserver(self, selector: #selector(handleGroupUpdatedNotification), name: .groupThreadUpdated, object: nil) notificationCenter.addObserver(self, selector: #selector(sendScreenshotNotificationIfNeeded), name: UIApplication.userDidTakeScreenshotNotification, object: nil) notificationCenter.addObserver(self, selector: #selector(handleMessageSentStatusChanged), name: .messageSentStatusDidChange, object: nil) + notificationCenter.addObserver(self, selector: #selector(handleContactThreadReplaced(_:)), name: .contactThreadReplaced, object: nil) // Mentions MentionsManager.populateUserPublicKeyCacheIfNeeded(for: thread.uniqueId!) // Draft @@ -428,6 +437,11 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + + // Don't set the draft or resign the first responder if we are replacing the thread (want the keyboard + // to appear to remain focussed) + guard !isReplacingThread else { return } + let text = snInputView.text Storage.write { transaction in self.thread.setDraft(text, transaction: transaction) @@ -693,6 +707,90 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat } } + @objc private func handleContactThreadReplaced(_ notification: Notification) { + // Ensure the current thread is one of the removed ones + guard let newThreadId: String = notification.userInfo?[NotificationUserInfoKey.threadId] as? String else { return } + guard let removedThreadIds: [String] = notification.userInfo?[NotificationUserInfoKey.removedThreadIds] as? [String] else { + return + } + guard let threadId: String = thread.uniqueId, removedThreadIds.contains(threadId) else { return } + + // Then look to swap the current ConversationVC with a replacement one with the new thread + DispatchQueue.main.async { + guard let navController: UINavigationController = self.navigationController else { return } + guard let viewControllerIndex: Int = navController.viewControllers.firstIndex(of: self) else { return } + guard let newThread: TSContactThread = TSContactThread.fetch(uniqueId: newThreadId) else { return } + + // Let the view controller know we are replacing the thread + self.isReplacingThread = true + + // Create the new ConversationVC and swap the old one out for it + let conversationVC: ConversationVC = ConversationVC(thread: newThread) + let currentlyOnThisScreen: Bool = (navController.topViewController == self) + + navController.viewControllers = [ + (viewControllerIndex == 0 ? + [] : + navController.viewControllers[0.. Bool { inputTextView.resignFirstResponder() } + + func inputTextViewBecomeFirstResponder() { + inputTextView.becomeFirstResponder() + } func handleLongPress() { // Not relevant in this case diff --git a/Session/Conversations/Message Cells/MessageCell.swift b/Session/Conversations/Message Cells/MessageCell.swift index 2c932517b..347c36668 100644 --- a/Session/Conversations/Message Cells/MessageCell.swift +++ b/Session/Conversations/Message Cells/MessageCell.swift @@ -66,5 +66,5 @@ protocol MessageCellDelegate : AnyObject { func openURL(_ url: URL) func handleReplyButtonTapped(for viewItem: ConversationViewItem) func showUserDetails(for sessionID: String) - func startThread(with sessionID: String, openGroupServer: String, openGroupPublicKey: String) + func startThread(with sessionId: String, openGroupServer: String, openGroupPublicKey: String) } diff --git a/Session/Utilities/ContactUtilities.swift b/Session/Utilities/ContactUtilities.swift index 2ecaa2641..f48fa32e2 100644 --- a/Session/Utilities/ContactUtilities.swift +++ b/Session/Utilities/ContactUtilities.swift @@ -1,23 +1,24 @@ enum ContactUtilities { + private static func approvedContact(in threadObject: Any, using transaction: Any) -> Contact? { + guard let thread: TSContactThread = threadObject as? TSContactThread else { return nil } + guard thread.shouldBeVisible else { return nil } + guard let contact: Contact = Storage.shared.getContact(with: thread.contactSessionID(), using: transaction) else { + return nil + } + guard contact.didApproveMe else { return nil } + + return contact + } static func getAllContacts() -> [String] { // Collect all contacts - var result: [String] = [] + var result: [Contact] = [] Storage.read { transaction in TSContactThread.enumerateCollectionObjects(with: transaction) { object, _ in - guard - let thread: TSContactThread = object as? TSContactThread, - thread.shouldBeVisible, - Storage.shared.getContact( - with: thread.contactSessionID(), - using: transaction - )?.didApproveMe == true - else { - return - } + guard let contact: Contact = approvedContact(in: object, using: transaction) else { return } - result.append(thread.contactSessionID()) + result.append(contact) } } func getDisplayName(for publicKey: String) -> String { @@ -25,11 +26,24 @@ enum ContactUtilities { } // Remove the current user - if let index = result.firstIndex(of: getUserHexEncodedPublicKey()) { + if let index = result.firstIndex(where: { $0.sessionID == getUserHexEncodedPublicKey() }) { result.remove(at: index) } // Sort alphabetically - return result.sorted { getDisplayName(for: $0) < getDisplayName(for: $1) } + return result + .map { contact -> String in (contact.displayName(for: .regular) ?? contact.sessionID) } + .sorted() + } + + static func enumerateApprovedContactThreads(with block: @escaping (TSContactThread, Contact, UnsafeMutablePointer) -> ()) { + Storage.read { transaction in + TSContactThread.enumerateCollectionObjects(with: transaction) { object, stop in + guard let contactThread: TSContactThread = object as? TSContactThread else { return } + guard let contact: Contact = approvedContact(in: object, using: transaction) else { return } + + block(contactThread, contact, stop) + } + } } } diff --git a/Session/Utilities/MockDataGenerator.swift b/Session/Utilities/MockDataGenerator.swift index 5a559b6df..dc5e2fcab 100644 --- a/Session/Utilities/MockDataGenerator.swift +++ b/Session/Utilities/MockDataGenerator.swift @@ -189,7 +189,8 @@ enum MockDataGenerator { image: nil, groupId: groupId, groupType: .closedGroup, - adminIds: [members.randomElement(using: &cgThreadRandomGenerator) ?? userSessionId] + adminIds: [members.randomElement(using: &cgThreadRandomGenerator) ?? userSessionId], + moderatorIds: [members.randomElement(using: &cgThreadRandomGenerator) ?? userSessionId] ) let thread = TSGroupThread.getOrCreateThread(with: group, transaction: transaction) thread.shouldBeVisible = true @@ -232,23 +233,49 @@ enum MockDataGenerator { let randomGroupPublicKey: String = KeyPairUtilities.generate(from: data).x25519KeyPair.hexEncodedPublicKey let serverNameLength: Int = ((5..<20).randomElement(using: &ogThreadRandomGenerator) ?? 0) let roomNameLength: Int = ((5..<20).randomElement(using: &ogThreadRandomGenerator) ?? 0) + let groupDescriptionLength: Int = ((10..<50).randomElement(using: &ogThreadRandomGenerator) ?? 0) let serverName: String = (0.. BlindedIdMapping? { + var result: BlindedIdMapping? + Storage.read { transaction in + result = self.getBlindedIdMapping(with: blindedId, using: transaction) + } + return result + } + + public func getBlindedIdMapping(with blindedId: String, using transaction: YapDatabaseReadTransaction) -> BlindedIdMapping? { + return transaction.object(forKey: blindedId, inCollection: Storage.blindedIdCacheCollection) as? BlindedIdMapping + } + + public func cacheBlindedIdMapping(_ mapping: BlindedIdMapping) { + Storage.write { transaction in + self.cacheBlindedIdMapping(mapping, using: transaction) + } + } + + public func cacheBlindedIdMapping(_ mapping: BlindedIdMapping, using transaction: YapDatabaseReadWriteTransaction) { + transaction.setObject(mapping, forKey: mapping.blindedId, inCollection: Storage.blindedIdCacheCollection) + } + + public func enumerateBlindedIdMapping(with block: @escaping (BlindedIdMapping, UnsafeMutablePointer) -> ()) { + Storage.read { transaction in + self.enumerateBlindedIdMapping(with: block, transaction: transaction) + } + } + + public func enumerateBlindedIdMapping(with block: @escaping (BlindedIdMapping, UnsafeMutablePointer) -> (), transaction: YapDatabaseReadTransaction) { + transaction.enumerateRows(inCollection: Storage.blindedIdCacheCollection) { _, object, _, stop in + guard let mapping = object as? BlindedIdMapping else { return } + + block(mapping, stop) + } + } } diff --git a/SessionMessagingKit/Database/Storage+Messaging.swift b/SessionMessagingKit/Database/Storage+Messaging.swift index 48421aa49..482bdc39a 100644 --- a/SessionMessagingKit/Database/Storage+Messaging.swift +++ b/SessionMessagingKit/Database/Storage+Messaging.swift @@ -2,35 +2,6 @@ import PromiseKit import Sodium extension Storage { - - public func getAllMessageRequestThreads() -> [String: TSContactThread] { - var result: [String: TSContactThread] = [:] - - Storage.read { transaction in - result = self.getAllMessageRequestThreads(using: transaction) - } - - return result - } - - public func getAllMessageRequestThreads(using transaction: YapDatabaseReadTransaction) -> [String: TSContactThread] { - var result = [String: TSContactThread]() - - // FIXME: We might be able to optimise this further by filtering the SQL query `WHERE uniqueId LIKE '_c15' - let blindedThreadPrefix: String = TSContactThread.threadID(fromContactSessionID: SessionId.Prefix.blinded.rawValue) - - transaction.enumerateKeysAndObjects( - inCollection: TSContactThread.collection(), - using: { threadID, object, _ in - guard let contactThread = object as? TSContactThread else { return } - result[threadID] = contactThread - }, - withFilter: { key -> Bool in key.starts(with: blindedThreadPrefix) } - ) - - return result - } - /// Returns the ID of the thread. public func getOrCreateThread(for publicKey: String, groupPublicKey: String?, openGroupID: String?, using transaction: Any) -> String? { let transaction = transaction as! YapDatabaseReadWriteTransaction @@ -180,5 +151,35 @@ extension Storage { let transaction = transaction as! YapDatabaseReadWriteTransaction transaction.setObject(receivedMessageTimestamps, forKey: "receivedMessageTimestamps", inCollection: Storage.receivedMessageTimestampsCollection) } + + // MARK: - Message Request Handling + + public func getAllMessageRequestThreads() -> [String: TSContactThread] { + var result: [String: TSContactThread] = [:] + + Storage.read { transaction in + result = self.getAllMessageRequestThreads(using: transaction) + } + + return result + } + + public func getAllMessageRequestThreads(using transaction: YapDatabaseReadTransaction) -> [String: TSContactThread] { + var result = [String: TSContactThread]() + + // FIXME: We might be able to optimise this further by filtering the SQL query `WHERE uniqueId LIKE '_c15' + let blindedThreadPrefix: String = TSContactThread.threadID(fromContactSessionID: SessionId.Prefix.blinded.rawValue) + + transaction.enumerateKeysAndObjects( + inCollection: TSContactThread.collection(), + using: { threadID, object, _ in + guard let contactThread = object as? TSContactThread else { return } + result[threadID] = contactThread + }, + withFilter: { key -> Bool in key.starts(with: blindedThreadPrefix) } + ) + + return result + } } diff --git a/SessionMessagingKit/Database/Storage+OpenGroups.swift b/SessionMessagingKit/Database/Storage+OpenGroups.swift index 07c8228a0..5031bfe69 100644 --- a/SessionMessagingKit/Database/Storage+OpenGroups.swift +++ b/SessionMessagingKit/Database/Storage+OpenGroups.swift @@ -55,35 +55,9 @@ extension Storage { return result } - public func storeOpenGroupServer(_ server: OpenGroupAPI.Server, using transaction: Any) { + public func setOpenGroupServer(_ server: OpenGroupAPI.Server, using transaction: Any) { (transaction as! YapDatabaseReadWriteTransaction).setObject(server, forKey: "SOGS.\(server.name)", inCollection: Storage.openGroupCollection) } - - // MARK: - Authorization - - private static let authTokenCollection = "SNAuthTokenCollection" - - public func getAuthToken(for room: String, on server: String) -> String? { - let collection = Storage.authTokenCollection - let key = "\(server).\(room)" - var result: String? = nil - Storage.read { transaction in - result = transaction.object(forKey: key, inCollection: collection) as? String - } - return result - } - - public func setAuthToken(for room: String, on server: String, to newValue: String, using transaction: Any) { - let collection = Storage.authTokenCollection - let key = "\(server).\(room)" - (transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: key, inCollection: collection) - } - - public func removeAuthToken(for room: String, on server: String, using transaction: Any) { - let collection = Storage.authTokenCollection - let key = "\(server).\(room)" - (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: key, inCollection: collection) - } @@ -109,12 +83,12 @@ extension Storage { - // MARK: - Last Message Server ID + // MARK: - Open Group Sequence Number - public static let lastMessageServerIDCollection = "SNLastMessageServerIDCollection" + public static let openGroupSequenceNumberCollection = "SNOpenGroupSequenceNumberCollection" - public func getLastMessageServerID(for room: String, on server: String) -> Int64? { - let collection = Storage.lastMessageServerIDCollection + public func getOpenGroupSequenceNumber(for room: String, on server: String) -> Int64? { + let collection = Storage.openGroupSequenceNumberCollection let key = "\(server).\(room)" var result: Int64? = nil Storage.read { transaction in @@ -123,48 +97,41 @@ extension Storage { return result } - public func setLastMessageServerID(for room: String, on server: String, to newValue: Int64, using transaction: Any) { - let collection = Storage.lastMessageServerIDCollection + public func setOpenGroupSequenceNumber(for room: String, on server: String, to newValue: Int64, using transaction: Any) { + let collection = Storage.openGroupSequenceNumberCollection let key = "\(server).\(room)" (transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: key, inCollection: collection) } - public func removeLastMessageServerID(for room: String, on server: String, using transaction: Any) { - let collection = Storage.lastMessageServerIDCollection + public func removeOpenGroupSequenceNumber(for room: String, on server: String, using transaction: Any) { + let collection = Storage.openGroupSequenceNumberCollection let key = "\(server).\(room)" (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: key, inCollection: collection) } + // MARK: - -- Open Group Inbox Latest Message Id + + public static let openGroupInboxLatestMessageIdCollection = "SNOpenGroupInboxLatestMessageIdCollection" - - // MARK: - Last Deletion Server ID - - public static let lastDeletionServerIDCollection = "SNLastDeletionServerIDCollection" - - public func getLastDeletionServerID(for room: String, on server: String) -> Int64? { - let collection = Storage.lastDeletionServerIDCollection - let key = "\(server).\(room)" + public func getOpenGroupInboxLatestMessageId(for server: String) -> Int64? { + let collection = Storage.openGroupInboxLatestMessageIdCollection var result: Int64? = nil Storage.read { transaction in - result = transaction.object(forKey: key, inCollection: collection) as? Int64 + result = transaction.object(forKey: server, inCollection: collection) as? Int64 } return result } - - public func setLastDeletionServerID(for room: String, on server: String, to newValue: Int64, using transaction: Any) { - let collection = Storage.lastDeletionServerIDCollection - let key = "\(server).\(room)" - (transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: key, inCollection: collection) + + public func setOpenGroupInboxLatestMessageId(for server: String, to newValue: Int64, using transaction: Any) { + let collection = Storage.openGroupInboxLatestMessageIdCollection + (transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: server, inCollection: collection) } - - public func removeLastDeletionServerID(for room: String, on server: String, using transaction: Any) { - let collection = Storage.lastDeletionServerIDCollection - let key = "\(server).\(room)" - (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: key, inCollection: collection) + + public func removeOpenGroupInboxLatestMessageId(for server: String, using transaction: Any) { + let collection = Storage.openGroupInboxLatestMessageIdCollection + (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: server, inCollection: collection) } - - // MARK: - Metadata private static let openGroupUserCountCollection = "SNOpenGroupUserCountCollection" diff --git a/SessionMessagingKit/Messages/Signal/TSInteraction.h b/SessionMessagingKit/Messages/Signal/TSInteraction.h index e6b77faf3..9dd99764c 100644 --- a/SessionMessagingKit/Messages/Signal/TSInteraction.h +++ b/SessionMessagingKit/Messages/Signal/TSInteraction.h @@ -79,6 +79,10 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value); - (void)updateTimestamp:(uint64_t)timestamp; +#pragma mark Message Request Thread Migration + +- (void)moveToThreadWithId:(NSString *)threadId; + @end NS_ASSUME_NONNULL_END diff --git a/SessionMessagingKit/Messages/Signal/TSInteraction.m b/SessionMessagingKit/Messages/Signal/TSInteraction.m index f3522712d..0ad46b0de 100644 --- a/SessionMessagingKit/Messages/Signal/TSInteraction.m +++ b/SessionMessagingKit/Messages/Signal/TSInteraction.m @@ -269,6 +269,12 @@ NSString *NSStringFromOWSInteractionType(OWSInteractionType value) } +#pragma mark - Message Request Thread Migration + +- (void)moveToThreadWithId:(NSString *)threadId { + _uniqueThreadId = threadId; +} + @end NS_ASSUME_NONNULL_END diff --git a/SessionMessagingKit/Open Groups/Models/Capabilities.swift b/SessionMessagingKit/Open Groups/Models/Capabilities.swift index 627183ace..3c5d7de12 100644 --- a/SessionMessagingKit/Open Groups/Models/Capabilities.swift +++ b/SessionMessagingKit/Open Groups/Models/Capabilities.swift @@ -35,6 +35,13 @@ extension OpenGroupAPI { public let capabilities: [Capability] public let missing: [Capability]? + + // MARK: - Initialization + + public init(capabilities: [Capability], missing: [Capability]? = nil) { + self.capabilities = capabilities + self.missing = missing + } } } diff --git a/SessionMessagingKit/Open Groups/OpenGroupManager.swift b/SessionMessagingKit/Open Groups/OpenGroupManager.swift index ab669549b..5a0bdeb1f 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupManager.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupManager.swift @@ -93,7 +93,6 @@ public final class OpenGroupManager: NSObject { } storage.updateMessageIDCollectionByPruningMessagesWithIDs(messageIDs, using: transaction) Storage.shared.removeReceivedMessageTimestamps(messageTimestamps, using: transaction) - let _ = OpenGroupAPI.legacyDeleteAuthToken(for: openGroup.room, on: openGroup.server) Storage.shared.removeOpenGroupSequenceNumber(for: openGroup.room, on: openGroup.server, using: transaction) thread.removeAllThreadInteractions(with: transaction) @@ -119,7 +118,7 @@ public final class OpenGroupManager: NSObject { capabilities: capabilities ) - dependencies.storage.storeOpenGroupServer(updatedServer, using: transaction) + dependencies.storage.setOpenGroupServer(updatedServer, using: transaction) } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index c030c9482..1915dd063 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -1,3 +1,5 @@ +import Foundation +import Sodium import SignalCoreKit import SessionSnodeKit @@ -238,8 +240,10 @@ extension MessageReceiver { thread.remove(with: transaction) } } - else { - // Otherwise create and save the thread + else if SessionId.Prefix(from: sessionID) != .blinded { + // Otherwise create and save the thread (if the contact isn't a blinded contact - we don't want to + // auto-create threads for blinded contacts if they have no messages) + // TODO: See what this will do with blinded->unblinded conversations? let thread = TSContactThread.getOrCreateThread(withContactSessionID: sessionID, transaction: transaction) thread.shouldBeVisible = true thread.save(with: transaction) @@ -839,26 +843,130 @@ extension MessageReceiver { public static func handleMessageRequestResponse(_ message: MessageRequestResponse, using transaction: Any) { let userPublicKey = getUserHexEncodedPublicKey() + var blindedContactIds: [String] = [] + var blindedThreadIds: [String] = [] // Ignore messages which were sent from the current user guard message.sender != userPublicKey else { return } guard let senderId: String = message.sender else { return } - - // Get the existing thead and notify the user - if let transaction: YapDatabaseReadWriteTransaction = transaction as? YapDatabaseReadWriteTransaction, let thread: TSContactThread = TSContactThread.getWithContactSessionID(senderId, transaction: transaction) { - let infoMessage = TSInfoMessage( - timestamp: (message.sentTimestamp ?? NSDate.ows_millisecondTimeStamp()), - in: thread, - messageType: .messageRequestAccepted - ) - infoMessage.save(with: transaction) + guard let transaction: YapDatabaseReadWriteTransaction = transaction as? YapDatabaseReadWriteTransaction else { + return } + // Prep the unblinded thread + let unblindedThreadId: String = TSContactThread.threadID(fromContactSessionID: senderId) + let unblindedThread: TSContactThread = TSContactThread.getOrCreateThread(withContactSessionID: senderId, transaction: transaction) + + // Need to handle a `MessageRequestResponse` sent to a blinded thread (ie. check if the sender matches + // the blinded ids of any threads) + let messageRequestThreads: [String: TSContactThread] = Storage.shared.getAllMessageRequestThreads(using: transaction) + + if !messageRequestThreads.isEmpty { + var interactionsToMove: [TSInteraction] = [] + var threadsToDelete: [TSContactThread] = [] + + // Loop through all blinded threads and extract any interactions relating to the user accepting + // the message request + for blindedThread in messageRequestThreads.values { + let blindedId: String = blindedThread.contactSessionID() + + // If the sessionId matches the blindedId then this thread needs to be converted to an un-blinded thread + guard let serverPublicKey: String = blindedThread.originalOpenGroupPublicKey else { continue } + guard Sodium().sessionId(senderId, matchesBlindedId: blindedId, serverPublicKey: serverPublicKey) else { continue } + guard let blindedThreadId: String = blindedThread.uniqueId else { continue } + guard let view: YapDatabaseAutoViewTransaction = transaction.ext(TSMessageDatabaseViewExtensionName) as? YapDatabaseAutoViewTransaction else { + continue + } + + // Cache the mapping + let mapping: BlindedIdMapping = BlindedIdMapping(blindedId: blindedId, sessionId: senderId, serverPublicKey: serverPublicKey) + Storage.shared.cacheBlindedIdMapping(mapping, using: transaction) + + // Add the `blindedId` to an array so we can remove them at the end of processing + blindedContactIds.append(blindedId) + blindedThreadIds.append(blindedThreadId) + + // Loop through all of the interactions and add them to a list to be moved to the new thread + view.enumerateRows(inGroup: blindedThreadId) { _, _, object, _, _, _ in + guard let interaction: TSInteraction = object as? TSInteraction else { + return + } + + interactionsToMove.append(interaction) + } + + threadsToDelete.append(blindedThread) + + // TODO: Pending jobs??? +// Storage.shared.getAllPendingJobs(of: <#T##Job.Type#>) + } + + // Sort the interactions by their `sortId` (which looks to be a global sort id for all interactions) just in case + // the behaviour changes in the future and the value can get reset (this way we process the interactions in the + // correct order regardless of how many threads they came from) + let sortedInteractionsToMove: [TSInteraction] = interactionsToMove + .sorted { lhs, rhs -> Bool in lhs.sortId < rhs.sortId } + + // Note: Unfortunately we need to move the interactions separately from enumerating them to avoid mutating the + // `TSMessageDatabaseViewExtensionName` while enumerating it (this does mean paying the cost of looping a second time) + for interaction in sortedInteractionsToMove { + interaction.moveToThread(withId: unblindedThreadId) + interaction.save(with: transaction) + } + + // Delete the old threads + for thread in threadsToDelete { + // TODO: This isn't updating the HomeVC... Race condition??? (Seems to not happen when stepping through with breakpoints) + thread.removeAllThreadInteractions(with: transaction) + thread.remove(with: transaction) + } + } + + // Update the `didApproveMe` state of the sender updateContactApprovalStatusIfNeeded( senderSessionId: senderId, threadId: nil, - forceConfigSync: true, + forceConfigSync: blindedContactIds.isEmpty, // Sync here if there are no blinded contacts using: transaction ) + + // If there were blinded contacts then we should remove them + if !blindedContactIds.isEmpty { + // Delete all of the processed blinded contacts (shouldn't need them anymore and don't want them taking up + // space in the config message) + for blindedId in blindedContactIds { + // TODO: OWSBlockingManager...??? + } + + // We should assume the 'sender' is a newly created contact and hence need to update it's `isApproved` state + updateContactApprovalStatusIfNeeded( + senderSessionId: userPublicKey, + threadId: unblindedThreadId, + forceConfigSync: true, + using: transaction + ) + } + + // Notify the user of their approval (Note: This will always appear in the un-blinded thread) + // Note: We want to do this last as it'll mean the un-blinded thread gets updated and the contact approval status + // will have been updated at this point (which will mean the `TSThread.isMessageRequest` will return correctly + // after this is saved + let infoMessage = TSInfoMessage( + timestamp: (message.sentTimestamp ?? NSDate.ows_millisecondTimeStamp()), + in: unblindedThread, + messageType: .messageRequestAccepted + ) + infoMessage.save(with: transaction) + + // Finally we need to send a notification that the thread was replaced so we can handle the case where the + // user might currently have the replaced thread open (only need to do this if we actually had blindedIds) + if !blindedThreadIds.isEmpty { + let userInfo: [NotificationUserInfoKey: Any] = [ + .threadId: unblindedThreadId, + .removedThreadIds: blindedThreadIds + ] + + NotificationCenter.default.post(name: .contactThreadReplaced, object: nil, userInfo: userInfo) + } } } diff --git a/SessionMessagingKit/Storage.swift b/SessionMessagingKit/Storage.swift index 2937c9de6..c73373d1e 100644 --- a/SessionMessagingKit/Storage.swift +++ b/SessionMessagingKit/Storage.swift @@ -60,7 +60,7 @@ public protocol SessionMessagingKitStorageProtocol { func setUserCount(to newValue: UInt64, forOpenGroupWithID openGroupID: String, using transaction: Any) func getOpenGroupServer(name: String) -> OpenGroupAPI.Server? - func storeOpenGroupServer(_ server: OpenGroupAPI.Server, using transaction: Any) + func setOpenGroupServer(_ server: OpenGroupAPI.Server, using transaction: Any) // MARK: - -- Open Group Public Keys diff --git a/SessionMessagingKit/Threads/Notification+Thread.swift b/SessionMessagingKit/Threads/Notification+Thread.swift index 4b61f8f1b..aad4c478f 100644 --- a/SessionMessagingKit/Threads/Notification+Thread.swift +++ b/SessionMessagingKit/Threads/Notification+Thread.swift @@ -4,6 +4,7 @@ public extension Notification.Name { static let groupThreadUpdated = Notification.Name("groupThreadUpdated") static let muteSettingUpdated = Notification.Name("muteSettingUpdated") static let messageSentStatusDidChange = Notification.Name("messageSentStatusDidChange") + static let contactThreadReplaced = Notification.Name("contactThreadReplaced") } @objc public extension NSNotification { @@ -12,3 +13,8 @@ public extension Notification.Name { @objc static let muteSettingUpdated = Notification.Name.muteSettingUpdated.rawValue as NSString @objc static let messageSentStatusDidChange = Notification.Name.messageSentStatusDidChange.rawValue as NSString } + +public enum NotificationUserInfoKey: String { + case threadId + case removedThreadIds +} diff --git a/SessionMessagingKit/Threads/TSContactThread.h b/SessionMessagingKit/Threads/TSContactThread.h index f40a7b98c..8a514c75b 100644 --- a/SessionMessagingKit/Threads/TSContactThread.h +++ b/SessionMessagingKit/Threads/TSContactThread.h @@ -10,13 +10,24 @@ extern NSString *const TSContactThreadPrefix; @interface TSContactThread : TSThread +@property (nonatomic, nullable) NSString *originalOpenGroupServer; +@property (nonatomic, nullable) NSString *originalOpenGroupPublicKey; + - (instancetype)initWithContactSessionID:(NSString *)contactSessionID; + (instancetype)getOrCreateThreadWithContactSessionID:(NSString *)contactSessionID NS_SWIFT_NAME(getOrCreateThread(contactSessionID:)); ++ (instancetype)getOrCreateThreadWithContactSessionID:(NSString *)contactSessionID + openGroupServer:(NSString *)openGroupServer + openGroupPublicKey:(NSString *)openGroupPublicKey NS_SWIFT_NAME(getOrCreateThread(contactSessionID:openGroupServer:openGroupPublicKey:)); + (instancetype)getOrCreateThreadWithContactSessionID:(NSString *)contactSessionID transaction:(YapDatabaseReadWriteTransaction *)transaction; ++ (instancetype)getOrCreateThreadWithContactSessionID:(NSString *)contactSessionID + openGroupServer:(NSString *)openGroupServer + openGroupPublicKey:(NSString *)openGroupPublicKey + transaction:(YapDatabaseReadWriteTransaction *)transaction; + // Unlike getOrCreateThreadWithContactSessionID, this will _NOT_ create a thread if one does not already exist. + (nullable instancetype)getThreadWithContactSessionID:(NSString *)contactSessionID transaction:(YapDatabaseReadTransaction *)transaction; diff --git a/SessionMessagingKit/Threads/TSContactThread.m b/SessionMessagingKit/Threads/TSContactThread.m index 0a2a1e9b6..5c4a9459c 100644 --- a/SessionMessagingKit/Threads/TSContactThread.m +++ b/SessionMessagingKit/Threads/TSContactThread.m @@ -33,6 +33,23 @@ NSString *const TSContactThreadPrefix = @"c"; return thread; } ++ (instancetype)getOrCreateThreadWithContactSessionID:(NSString *)contactSessionID + openGroupServer:(NSString *)openGroupServer + openGroupPublicKey:(NSString *)openGroupPublicKey + transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + TSContactThread *thread = [self fetchObjectWithUniqueID:[self threadIDFromContactSessionID:contactSessionID] transaction:transaction]; + + if (!thread) { + thread = [[TSContactThread alloc] initWithContactSessionID:contactSessionID]; + thread.originalOpenGroupServer = openGroupServer; + thread.originalOpenGroupPublicKey = openGroupPublicKey; + [thread saveWithTransaction:transaction]; + } + + return thread; +} + + (instancetype)getOrCreateThreadWithContactSessionID:(NSString *)contactSessionID { __block TSContactThread *thread; @@ -43,6 +60,18 @@ NSString *const TSContactThreadPrefix = @"c"; return thread; } ++ (instancetype)getOrCreateThreadWithContactSessionID:(NSString *)contactSessionID + openGroupServer:(NSString *)openGroupServer + openGroupPublicKey:(NSString *)openGroupPublicKey +{ + __block TSContactThread *thread; + [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + thread = [self getOrCreateThreadWithContactSessionID:contactSessionID openGroupServer:openGroupServer openGroupPublicKey:openGroupPublicKey transaction:transaction]; + }]; + + return thread; +} + + (nullable instancetype)getThreadWithContactSessionID:(NSString *)contactSessionID transaction:(YapDatabaseReadTransaction *)transaction; { return [TSContactThread fetchObjectWithUniqueID:[self threadIDFromContactSessionID:contactSessionID] transaction:transaction]; diff --git a/SessionMessagingKitTests/_TestUtilities/TestStorage.swift b/SessionMessagingKitTests/_TestUtilities/TestStorage.swift index 1128e0f9c..535a1a43f 100644 --- a/SessionMessagingKitTests/_TestUtilities/TestStorage.swift +++ b/SessionMessagingKitTests/_TestUtilities/TestStorage.swift @@ -86,7 +86,7 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable { func getOpenGroup(for threadID: String) -> OpenGroup? { return (mockData[.openGroup] as? OpenGroup) } func setOpenGroup(_ openGroup: OpenGroup, for threadID: String, using transaction: Any) { mockData[.openGroup] = openGroup } func getOpenGroupServer(name: String) -> OpenGroupAPI.Server? { return mockData[.openGroupServer] as? OpenGroupAPI.Server } - func storeOpenGroupServer(_ server: OpenGroupAPI.Server, using transaction: Any) { mockData[.openGroupServer] = server } + func setOpenGroupServer(_ server: OpenGroupAPI.Server, using transaction: Any) { mockData[.openGroupServer] = server } func getUserCount(forOpenGroupWithID openGroupID: String) -> UInt64? { return (mockData[.openGroupUserCount] as? UInt64) diff --git a/SignalUtilitiesKit/Database/Migrations/SOGSV4Migration.swift b/SignalUtilitiesKit/Database/Migrations/SOGSV4Migration.swift new file mode 100644 index 000000000..2737611b1 --- /dev/null +++ b/SignalUtilitiesKit/Database/Migrations/SOGSV4Migration.swift @@ -0,0 +1,33 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +@objc(SNSOGSV4Migration) +public class SOGSV4Migration: OWSDatabaseMigration { + + @objc + class func migrationId() -> String { + return "003" + } + + override public func runUp(completion: @escaping OWSDatabaseMigrationCompletion) { + self.doMigrationAsync(completion: completion) + } + + private func doMigrationAsync(completion: @escaping OWSDatabaseMigrationCompletion) { + // These collections became redundant in SOGS V4 + let lastMessageServerIDCollection: String = "SNLastMessageServerIDCollection" + let lastDeletionServerIDCollection: String = "SNLastDeletionServerIDCollection" + let authTokenCollection: String = "SNAuthTokenCollection" + + Storage.write(with: { transaction in + transaction.removeAllObjects(inCollection: lastMessageServerIDCollection) + transaction.removeAllObjects(inCollection: lastDeletionServerIDCollection) + transaction.removeAllObjects(inCollection: authTokenCollection) + + self.save(with: transaction) // Intentionally capture self + }, completion: { + completion() + }) + } +}