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)
This commit is contained in:
Morgan Pretty 2022-03-01 14:06:37 +11:00
parent 3e97782d18
commit a26ee12f8d
24 changed files with 618 additions and 132 deletions

View File

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

View File

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

View File

@ -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 = "<group>"; };
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 = "<group>"; };
FD0BA51C27CDC34600CC6805 /* SOGSV4Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSV4Migration.swift; sourceTree = "<group>"; };
FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestResponse.swift; sourceTree = "<group>"; };
FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = "<group>"; };
FD5D201D27B0D87C00FEA984 /* SessionId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionId.swift; sourceTree = "<group>"; };
@ -2564,6 +2568,7 @@
isa = PBXGroup;
children = (
B8B32020258B1A650020074B /* Contact.swift */,
FD0BA51A27CD88EC00CC6805 /* BlindedIdMapping.swift */,
);
path = Contacts;
sourceTree = "<group>";
@ -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 */,

View File

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

View File

@ -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..<viewControllerIndex]
),
[conversationVC],
(viewControllerIndex == (navController.viewControllers.count - 1) ?
[] :
navController.viewControllers[(viewControllerIndex + 1)..<navController.viewControllers.count]
)
].flatMap { $0 }
// If the top vew controller isn't the current one then we need to make sure to swap out child ones as well
if !currentlyOnThisScreen {
let maybeSettingsViewController: UIViewController? = navController
.viewControllers[viewControllerIndex..<navController.viewControllers.count]
.first(where: { $0 is OWSConversationSettingsViewController })
// Update the settings screen (if there is one)
if let settingsViewController: OWSConversationSettingsViewController = maybeSettingsViewController as? OWSConversationSettingsViewController {
settingsViewController.configure(with: newThread, uiDatabaseConnection: OWSPrimaryStorage.shared().uiDatabaseConnection)
}
}
// Try to minimise painful UX issues by keeping the 'first responder' state, current input text and
// cursor position (Unfortunately there doesn't seem to be a way to prevent the keyboard from
// flickering during the swap but other than that it's relatively seamless)
if self.snInputView.inputTextViewIsFirstResponder {
conversationVC.isReplacingThread = true
conversationVC.snInputView.frame = self.snInputView.frame
conversationVC.snInputView.text = self.snInputView.text
conversationVC.snInputView.selectedRange = self.snInputView.selectedRange
// Make the current snInputView invisible and add the new one the the UI
self.snInputView.alpha = 0
self.snInputView.superview?.addSubview(conversationVC.snInputView)
// Add the old first responder to the window so it the keyboard won't get dismissed when the
// OS removes it's parent view from the view hierarchy due to the view controller swap
var maybeOldFirstResponderView: UIView?
if let oldFirstResponderView: UIView = UIResponder.currentFirstResponder() as? UIView {
maybeOldFirstResponderView = oldFirstResponderView
self.view.window?.addSubview(oldFirstResponderView)
}
// On the next run loop setup the first responder state for the new screen and remove the
// old first responder from the window
DispatchQueue.main.async {
UIView.performWithoutAnimation {
conversationVC.isReplacingThread = false
maybeOldFirstResponderView?.resignFirstResponder()
maybeOldFirstResponderView?.removeFromSuperview()
conversationVC.snInputView.removeFromSuperview()
_ = conversationVC.becomeFirstResponder()
conversationVC.snInputView.inputTextViewBecomeFirstResponder()
}
}
}
}
}
// MARK: General
@objc func addOrRemoveBlockedBanner() {
func detach() {

View File

@ -24,6 +24,13 @@ final class InputView : UIView, InputViewButtonDelegate, InputTextViewDelegate,
set { inputTextView.text = newValue }
}
var selectedRange: NSRange {
get { inputTextView.selectedRange }
set { inputTextView.selectedRange = newValue }
}
var inputTextViewIsFirstResponder: Bool { inputTextView.isFirstResponder }
var enabledMessageTypes: MessageTypes = .all {
didSet {
setEnabledMessageTypes(enabledMessageTypes, message: nil)
@ -336,6 +343,10 @@ final class InputView : UIView, InputViewButtonDelegate, InputTextViewDelegate,
override func resignFirstResponder() -> Bool {
inputTextView.resignFirstResponder()
}
func inputTextViewBecomeFirstResponder() {
inputTextView.becomeFirstResponder()
}
func handleLongPress() {
// Not relevant in this case

View File

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

View File

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

View File

@ -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..<serverNameLength)
.compactMap { _ in stringContent.randomElement(using: &ogThreadRandomGenerator) }
.joined()
let roomName: String = (0..<roomNameLength)
.compactMap { _ in stringContent.randomElement(using: &ogThreadRandomGenerator) }
.joined()
let groupDescription: String = (0..<groupDescriptionLength)
.compactMap { _ in stringContent.randomElement(using: &ogThreadRandomGenerator) }
.joined()
// Create the open group model and the thread
let openGroup: OpenGroupV2 = OpenGroupV2(server: serverName, room: roomName, name: roomName, publicKey: randomGroupPublicKey, imageID: nil)
let openGroup: OpenGroup = OpenGroup(
server: serverName,
room: roomName,
publicKey: randomGroupPublicKey,
name: roomName,
groupDescription: groupDescription,
imageID: nil,
infoUpdates: 0
)
let groupId: Data = LKGroupUtilities.getEncodedOpenGroupIDAsData(openGroup.id)
let model = TSGroupModel(title: openGroup.name, memberIds: [ userSessionId ], image: nil, groupId: groupId, groupType: .openGroup, adminIds: [])
let model = TSGroupModel(title: openGroup.name, memberIds: [ userSessionId ], image: nil, groupId: groupId, groupType: .openGroup, adminIds: [], moderatorIds: [])
let thread = TSGroupThread.getOrCreateThread(with: model, transaction: transaction)
thread.shouldBeVisible = true
thread.save(with: transaction)
Storage.shared.setV2OpenGroup(openGroup, for: thread.uniqueId!, using: transaction)
Storage.shared.setOpenGroup(openGroup, for: thread.uniqueId!, using: transaction)
// Generate the 'Server' object
let hasBlinding: Bool = Bool.random(using: &dmThreadRandomGenerator)
let server: OpenGroupAPI.Server = OpenGroupAPI.Server(
name: serverName,
capabilities: OpenGroupAPI.Capabilities(
capabilities: [.sogs]
.appending(hasBlinding ? [.blind] : []),
missing: nil
)
)
Storage.shared.setOpenGroupServer(server, using: transaction)
}
}
}

View File

@ -0,0 +1,40 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
@objc(SNBlindedIdMapping)
public class BlindedIdMapping: NSObject, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility
@objc public let blindedId: String
@objc public let sessionId: String
@objc public let serverPublicKey: String
// MARK: - Initialization
@objc public init(blindedId: String, sessionId: String, serverPublicKey: String) {
self.blindedId = blindedId
self.sessionId = sessionId
self.serverPublicKey = serverPublicKey
super.init()
}
private override init() { preconditionFailure("Use init(blindedId:sessionId:) instead.") }
// MARK: - Coding
public required init?(coder: NSCoder) {
guard let blindedId: String = coder.decodeObject(forKey: "blindedId") as! String? else { return nil }
guard let sessionId: String = coder.decodeObject(forKey: "sessionId") as! String? else { return nil }
guard let serverPublicKey: String = coder.decodeObject(forKey: "serverPublicKey") as! String? else { return nil }
self.blindedId = blindedId
self.sessionId = sessionId
self.serverPublicKey = serverPublicKey
}
public func encode(with coder: NSCoder) {
coder.encode(blindedId, forKey: "blindedId")
coder.encode(sessionId, forKey: "sessionId")
coder.encode(serverPublicKey, forKey: "serverPublicKey")
}
}

View File

@ -68,4 +68,44 @@ extension Storage {
}
return result
}
// MARK: - Blinded Id cache
private static let blindedIdCacheCollection = "BlindedIdCacheCollection"
public func getBlindedIdMapping(with blindedId: String) -> 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<ObjCBool>) -> ()) {
Storage.read { transaction in
self.enumerateBlindedIdMapping(with: block, transaction: transaction)
}
}
public func enumerateBlindedIdMapping(with block: @escaping (BlindedIdMapping, UnsafeMutablePointer<ObjCBool>) -> (), transaction: YapDatabaseReadTransaction) {
transaction.enumerateRows(inCollection: Storage.blindedIdCacheCollection) { _, object, _, stop in
guard let mapping = object as? BlindedIdMapping else { return }
block(mapping, stop)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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