Merge pull request #881 from mpretty-cyro/feature/make-user-config-permanent

Make Updated User Config Permanent
This commit is contained in:
Morgan Pretty 2023-08-11 17:59:38 +10:00 committed by GitHub
commit 9eb7a6af6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 29 additions and 1018 deletions

View File

@ -128,7 +128,6 @@
7B8C44C528B49DDA00FBE25F /* NewConversationVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8C44C428B49DDA00FBE25F /* NewConversationVC.swift */; };
7B8D5FC428332600008324D9 /* VisibleMessage+Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */; };
7B93D06A27CF173D00811CB6 /* MessageRequestsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */; };
7B93D07027CF194000811CB6 /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */; };
7B93D07127CF194000811CB6 /* MessageRequestResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06F27CF194000811CB6 /* MessageRequestResponse.swift */; };
7B93D07727CF1A8A00811CB6 /* MockDataGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D07527CF1A8900811CB6 /* MockDataGenerator.swift */; };
7B9F71C928470667006DFE7B /* ReactionListSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B9F71C828470667006DFE7B /* ReactionListSheet.swift */; };
@ -652,7 +651,6 @@
FD5C72F9284F0E880029977D /* MessageReceiver+TypingIndicators.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */; };
FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */; };
FD5C72FD284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FC284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift */; };
FD5C72FF284F0F120029977D /* MessageReceiver+ConfigurationMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FE284F0F120029977D /* MessageReceiver+ConfigurationMessages.swift */; };
FD5C7301284F0F7A0029977D /* MessageReceiver+UnsendRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C7300284F0F7A0029977D /* MessageReceiver+UnsendRequests.swift */; };
FD5C7303284F0FA50029977D /* MessageReceiver+Calls.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C7302284F0FA50029977D /* MessageReceiver+Calls.swift */; };
FD5C7305284F0FF30029977D /* MessageReceiver+VisibleMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C7304284F0FF30029977D /* MessageReceiver+VisibleMessages.swift */; };
@ -1237,7 +1235,6 @@
7B8C44C428B49DDA00FBE25F /* NewConversationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationVC.swift; sourceTree = "<group>"; };
7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+Reaction.swift"; sourceTree = "<group>"; };
7B93D06927CF173D00811CB6 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = "<group>"; };
7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = "<group>"; };
7B93D06F27CF194000811CB6 /* MessageRequestResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestResponse.swift; sourceTree = "<group>"; };
7B93D07527CF1A8900811CB6 /* MockDataGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockDataGenerator.swift; sourceTree = "<group>"; };
7B9F71C828470667006DFE7B /* ReactionListSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionListSheet.swift; sourceTree = "<group>"; };
@ -1771,7 +1768,6 @@
FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+TypingIndicators.swift"; sourceTree = "<group>"; };
FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+DataExtractionNotification.swift"; sourceTree = "<group>"; };
FD5C72FC284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ExpirationTimers.swift"; sourceTree = "<group>"; };
FD5C72FE284F0F120029977D /* MessageReceiver+ConfigurationMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ConfigurationMessages.swift"; sourceTree = "<group>"; };
FD5C7300284F0F7A0029977D /* MessageReceiver+UnsendRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+UnsendRequests.swift"; sourceTree = "<group>"; };
FD5C7302284F0FA50029977D /* MessageReceiver+Calls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+Calls.swift"; sourceTree = "<group>"; };
FD5C7304284F0FF30029977D /* MessageReceiver+VisibleMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+VisibleMessages.swift"; sourceTree = "<group>"; };
@ -2730,7 +2726,6 @@
C34A977325A3E34A00852C71 /* ClosedGroupControlMessage.swift */,
FD8ECF8A2935DB4B00C0D1BB /* SharedConfigMessage.swift */,
C3DA9C0625AE7396008F7C7E /* ConfigurationMessage.swift */,
7B93D06E27CF194000811CB6 /* ConfigurationMessage+Convenience.swift */,
B8F5F60225EDE16F003BF8D4 /* DataExtractionNotification.swift */,
C300A5E62554B07300555489 /* ExpirationTimerUpdate.swift */,
7B93D06F27CF194000811CB6 /* MessageRequestResponse.swift */,
@ -4002,7 +3997,6 @@
FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */,
FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */,
FD5C72FC284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift */,
FD5C72FE284F0F120029977D /* MessageReceiver+ConfigurationMessages.swift */,
FD5C7300284F0F7A0029977D /* MessageReceiver+UnsendRequests.swift */,
FD5C7302284F0FA50029977D /* MessageReceiver+Calls.swift */,
FD5C7304284F0FF30029977D /* MessageReceiver+VisibleMessages.swift */,
@ -5864,7 +5858,6 @@
FD23CE222A661D000000B97C /* OpenGroupAPI+Crypto.swift in Sources */,
FD245C652850665400B966DD /* ClosedGroupControlMessage.swift in Sources */,
FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */,
7B93D07027CF194000811CB6 /* ConfigurationMessage+Convenience.swift in Sources */,
FD5C72FD284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift in Sources */,
C32C5A88256DBCF9003C73A2 /* MessageReceiver+ClosedGroups.swift in Sources */,
B8D0A25925E367AC00C1835E /* Notification+MessageReceiver.swift in Sources */,
@ -5874,7 +5867,6 @@
C32C599E256DB02B003C73A2 /* TypingIndicators.swift in Sources */,
FD716E682850318E00C96BF4 /* CallMode.swift in Sources */,
FD09799527FE7B8E00936362 /* Interaction.swift in Sources */,
FD5C72FF284F0F120029977D /* MessageReceiver+ConfigurationMessages.swift in Sources */,
FD37EA0D28AB2A45003AE748 /* _005_FixDeletedMessageReadState.swift in Sources */,
7BAA7B6628D2DE4700AE1489 /* _009_OpenGroupPermission.swift in Sources */,
FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */,

View File

@ -283,14 +283,6 @@ final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewData
// Start polling if needed (i.e. if the user just created or restored their Session ID)
if Identity.userExists(), let appDelegate: AppDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.startPollersIfNeeded()
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
if !SessionUtil.userConfigsEnabled {
// Do this only if we created a new Session ID, or if we already received the initial configuration message
if UserDefaults.standard[.hasSyncedInitialConfiguration] {
appDelegate.syncConfigurationIfNeeded()
}
}
}
// Onion request path countries cache

View File

@ -522,7 +522,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
startPollersIfNeeded()
if CurrentAppContext().isMainApp {
syncConfigurationIfNeeded()
handleAppActivatedWithOngoingCallIfNeeded()
}
}
@ -868,36 +867,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
presentingVC.present(callVC, animated: true, completion: nil)
}
// MARK: - Config Sync
func syncConfigurationIfNeeded() {
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
guard !SessionUtil.userConfigsEnabled else { return }
let lastSync: Date = (UserDefaults.standard[.lastConfigurationSync] ?? .distantPast)
guard Date().timeIntervalSince(lastSync) > (7 * 24 * 60 * 60) else { return } // Sync every 2 days
Storage.shared
.writeAsync(
updates: { db in
ConfigurationSyncJob.enqueue(db, publicKey: getUserHexEncodedPublicKey(db))
},
completion: { _, result in
switch result {
case .failure: break
case .success:
// Only update the 'lastConfigurationSync' timestamp if we have done the
// first sync (Don't want a new device config sync to override config
// syncs from other devices)
if UserDefaults.standard[.hasSyncedInitialConfiguration] {
UserDefaults.standard[.lastConfigurationSync] = Date()
}
}
}
)
}
}
// MARK: - LifecycleMethod

View File

@ -30,13 +30,6 @@ enum Onboarding {
_ requestId: UUID,
using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<String?, Error> {
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
guard SessionUtil.userConfigsEnabled else {
return Just(nil)
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
let userPublicKey: String = getUserHexEncodedPublicKey()
return SnodeAPI.getSwarm(for: userPublicKey)

View File

@ -31,14 +31,8 @@ public enum SNMessagingKit: MigratableTarget { // Just to make the external API
_011_AddPendingReadReceipts.self,
_012_AddFTSIfNeeded.self,
_013_SessionUtilChanges.self,
// Wait until the feature is turned on before doing the migration that generates
// the config dump data
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
(Features.useSharedUtilForUserConfig(db) ?
_014_GenerateInitialUserConfigDumps.self :
(nil as Migration.Type?)
)
].compactMap { $0 }
_014_GenerateInitialUserConfigDumps.self
]
]
)
}

View File

@ -649,23 +649,18 @@ public enum SMKLegacy {
@objc(SNConfigurationMessage)
internal final class _ConfigurationMessage: _ControlMessage {
internal var closedGroups: Set<_CMClosedGroup> = []
internal var openGroups: Set<String> = []
internal var displayName: String?
internal var profilePictureURL: String?
internal var profileKey: Data?
internal var contacts: Set<_CMContact> = []
// MARK: NSCoding
public required init?(coder: NSCoder) {
super.init(coder: coder)
if let closedGroups = coder.decodeObject(forKey: "closedGroups") as! Set<_CMClosedGroup>? { self.closedGroups = closedGroups }
if let openGroups = coder.decodeObject(forKey: "openGroups") as! Set<String>? { self.openGroups = openGroups }
if let displayName = coder.decodeObject(forKey: "displayName") as! String? { self.displayName = displayName }
if let profilePictureURL = coder.decodeObject(forKey: "profilePictureURL") as! String? { self.profilePictureURL = profilePictureURL }
if let profileKey = coder.decodeObject(forKey: "profileKey") as! Data? { self.profileKey = profileKey }
if let contacts = coder.decodeObject(forKey: "contacts") as! Set<_CMContact>? { self.contacts = contacts }
}
public override func encode(with coder: NSCoder) {
@ -679,126 +674,12 @@ public enum SMKLegacy {
ConfigurationMessage(
displayName: displayName,
profilePictureUrl: profilePictureURL,
profileKey: profileKey,
closedGroups: closedGroups
.map { $0.toNonLegacy() }
.asSet(),
openGroups: openGroups,
contacts: contacts
.map { $0.toNonLegacy() }
.asSet()
profileKey: profileKey
)
)
}
}
// MARK: - Config Message Closed Group
@objc(CMClosedGroup)
internal final class _CMClosedGroup: NSObject, NSCoding {
internal let publicKey: String
internal let name: String
internal let encryptionKeyPair: SUKLegacy.KeyPair
internal let members: Set<String>
internal let admins: Set<String>
internal let expirationTimer: UInt32
// MARK: NSCoding
public required init?(coder: NSCoder) {
guard
let publicKey = coder.decodeObject(forKey: "publicKey") as! String?,
let name = coder.decodeObject(forKey: "name") as! String?,
let encryptionKeyPair = coder.decodeObject(forKey: "encryptionKeyPair") as! SUKLegacy.KeyPair?,
let members = coder.decodeObject(forKey: "members") as! Set<String>?,
let admins = coder.decodeObject(forKey: "admins") as! Set<String>?
else { return nil }
self.publicKey = publicKey
self.name = name
self.encryptionKeyPair = encryptionKeyPair
self.members = members
self.admins = admins
self.expirationTimer = (coder.decodeObject(forKey: "expirationTimer") as? UInt32 ?? 0)
}
public func encode(with coder: NSCoder) {
fatalError("encode(with:) should never be called for legacy types")
}
// MARK: Non-Legacy Conversion
internal func toNonLegacy() -> ConfigurationMessage.CMClosedGroup {
return ConfigurationMessage.CMClosedGroup(
publicKey: publicKey,
name: name,
encryptionKeyPublicKey: encryptionKeyPair.publicKey,
encryptionKeySecretKey: encryptionKeyPair.privateKey,
members: members,
admins: admins,
expirationTimer: expirationTimer
)
}
}
// MARK: - Config Message Contact
@objc(SNConfigurationMessageContact)
internal final class _CMContact: NSObject, NSCoding {
internal var publicKey: String?
internal var displayName: String?
internal var profilePictureURL: String?
internal var profileKey: Data?
internal var hasIsApproved: Bool
internal var isApproved: Bool
internal var hasIsBlocked: Bool
internal var isBlocked: Bool
internal var hasDidApproveMe: Bool
internal var didApproveMe: Bool
// MARK: NSCoding
public required init?(coder: NSCoder) {
guard
let publicKey = coder.decodeObject(forKey: "publicKey") as! String?,
let displayName = coder.decodeObject(forKey: "displayName") as! String?
else { return nil }
self.publicKey = publicKey
self.displayName = displayName
self.profilePictureURL = coder.decodeObject(forKey: "profilePictureURL") as! String?
self.profileKey = coder.decodeObject(forKey: "profileKey") as! Data?
self.hasIsApproved = (coder.decodeObject(forKey: "hasIsApproved") as? Bool ?? false)
self.isApproved = (coder.decodeObject(forKey: "isApproved") as? Bool ?? false)
self.hasIsBlocked = (coder.decodeObject(forKey: "hasIsBlocked") as? Bool ?? false)
self.isBlocked = (coder.decodeObject(forKey: "isBlocked") as? Bool ?? false)
self.hasDidApproveMe = (coder.decodeObject(forKey: "hasDidApproveMe") as? Bool ?? false)
self.didApproveMe = (coder.decodeObject(forKey: "didApproveMe") as? Bool ?? false)
}
public func encode(with coder: NSCoder) {
fatalError("encode(with:) should never be called for legacy types")
}
// MARK: Non-Legacy Conversion
internal func toNonLegacy() -> ConfigurationMessage.CMContact {
return ConfigurationMessage.CMContact(
publicKey: publicKey,
displayName: displayName,
profilePictureUrl: profilePictureURL,
profileKey: profileKey,
hasIsApproved: hasIsApproved,
isApproved: isApproved,
hasIsBlocked: hasIsBlocked,
isBlocked: isBlocked,
hasDidApproveMe: hasDidApproveMe,
didApproveMe: didApproveMe
)
}
}
// MARK: - Unsend Request
@objc(SNUnsendRequest)

View File

@ -1851,14 +1851,6 @@ enum _003_YDBToGRDBMigration: Migration {
SMKLegacy._ConfigurationMessage.self,
forClassName: "SNConfigurationMessage"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._CMClosedGroup.self,
forClassName: "SNClosedGroup"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._CMContact.self,
forClassName: "SNConfigurationMessage.SNConfigurationMessageContact"
)
NSKeyedUnarchiver.setClass(
SMKLegacy._UnsendRequest.self,
forClassName: "SNUnsendRequest"

View File

@ -6,8 +6,6 @@ import SessionUtil
import SessionUtilitiesKit
/// This migration goes through the current state of the database and generates config dumps for the user config types
///
/// **Note:** This migration won't be run until the `useSharedUtilForUserConfig` feature flag is enabled
enum _014_GenerateInitialUserConfigDumps: Migration {
static let target: TargetMigrations.Identifier = .messagingKit
static let identifier: String = "GenerateInitialUserConfigDumps"

View File

@ -20,10 +20,7 @@ public enum ConfigurationSyncJob: JobExecutor {
deferred: @escaping (Job, Dependencies) -> (),
using dependencies: Dependencies
) {
guard
SessionUtil.userConfigsEnabled,
Identity.userCompletedRequiredOnboarding()
else { return success(job, true, dependencies) }
guard Identity.userCompletedRequiredOnboarding() else { return success(job, true, dependencies) }
// It's possible for multiple ConfigSyncJob's with the same target (user/group) to try to run at the
// same time since as soon as one is started we will enqueue a second one, rather than adding dependencies
@ -200,35 +197,6 @@ public extension ConfigurationSyncJob {
publicKey: String,
dependencies: Dependencies = Dependencies()
) {
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
guard SessionUtil.userConfigsEnabled(db) else {
// If we don't have a userKeyPair (or name) yet then there is no need to sync the
// configuration as the user doesn't fully exist yet (this will get triggered on
// the first launch of a fresh install due to the migrations getting run and a few
// times during onboarding)
guard
Identity.userCompletedRequiredOnboarding(db),
let legacyConfigMessage: Message = try? ConfigurationMessage.getCurrent(db)
else { return }
let publicKey: String = getUserHexEncodedPublicKey(db)
dependencies.jobRunner.add(
db,
job: Job(
variant: .messageSend,
threadId: publicKey,
details: MessageSendJob.Details(
destination: Message.Destination.contact(publicKey: publicKey),
message: legacyConfigMessage
)
),
canStartJob: true,
using: dependencies
)
return
}
// Upsert a config sync job if needed
dependencies.jobRunner.upsert(
db,
@ -268,30 +236,6 @@ public extension ConfigurationSyncJob {
}
static func run(using dependencies: Dependencies = Dependencies()) -> AnyPublisher<Void, Error> {
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
guard SessionUtil.userConfigsEnabled else {
return Storage.shared
.writePublisher { db -> MessageSender.PreparedSendData in
// If we don't have a userKeyPair yet then there is no need to sync the configuration
// as the user doesn't exist yet (this will get triggered on the first launch of a
// fresh install due to the migrations getting run)
guard Identity.userCompletedRequiredOnboarding(db) else { throw StorageError.generic }
let publicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
return try MessageSender.preparedSendData(
db,
message: try ConfigurationMessage.getCurrent(db),
to: Message.Destination.contact(publicKey: publicKey),
namespace: .default,
interactionId: nil,
using: dependencies
)
}
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
.eraseToAnyPublisher()
}
// Trigger the job emitting the result when completed
return Deferred {
Future { resolver in

View File

@ -1,85 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
extension ConfigurationMessage {
public static func getCurrent(_ db: Database) throws -> ConfigurationMessage {
let currentUserProfile: Profile = Profile.fetchOrCreateCurrentUser(db)
let displayName: String = currentUserProfile.name
let profilePictureUrl: String? = currentUserProfile.profilePictureUrl
let profileKey: Data? = currentUserProfile.profileEncryptionKey
let closedGroups: Set<CMClosedGroup> = try ClosedGroup.fetchAll(db)
.compactMap { closedGroup -> CMClosedGroup? in
guard let latestKeyPair: ClosedGroupKeyPair = try closedGroup.fetchLatestKeyPair(db) else {
return nil
}
return CMClosedGroup(
publicKey: closedGroup.publicKey,
name: closedGroup.name,
encryptionKeyPublicKey: latestKeyPair.publicKey,
encryptionKeySecretKey: latestKeyPair.secretKey,
members: try closedGroup.members
.select(GroupMember.Columns.profileId)
.asRequest(of: String.self)
.fetchSet(db),
admins: try closedGroup.admins
.select(GroupMember.Columns.profileId)
.asRequest(of: String.self)
.fetchSet(db),
expirationTimer: (try? DisappearingMessagesConfiguration
.fetchOne(db, id: closedGroup.threadId)
.map { ($0.isEnabled ? UInt32($0.durationSeconds) : 0) })
.defaulting(to: 0)
)
}
.asSet()
// The default room promise creates an OpenGroup with an empty `roomToken` value,
// we don't want to start a poller for this as the user hasn't actually joined a room
let openGroups: Set<String> = try OpenGroup
.filter(OpenGroup.Columns.roomToken != "")
.filter(OpenGroup.Columns.isActive)
.fetchAll(db)
.compactMap { openGroup in
SessionUtil.communityUrlFor(
server: openGroup.server,
roomToken: openGroup.roomToken,
publicKey: openGroup.publicKey
)
}
.asSet()
let contacts: Set<CMContact> = try Contact
.filter(Contact.Columns.id != currentUserProfile.id)
.fetchAll(db)
.map { contact -> CMContact in
// Can just default the 'hasX' values to true as they will be set to this
// when converting to proto anyway
let profile: Profile? = try? Profile.fetchOne(db, id: contact.id)
return CMContact(
publicKey: contact.id,
displayName: (profile?.name ?? contact.id),
profilePictureUrl: profile?.profilePictureUrl,
profileKey: profile?.profileEncryptionKey,
hasIsApproved: true,
isApproved: contact.isApproved,
hasIsBlocked: true,
isBlocked: contact.isBlocked,
hasDidApproveMe: true,
didApproveMe: contact.didApproveMe
)
}
.asSet()
return ConfigurationMessage(
displayName: displayName,
profilePictureUrl: profilePictureUrl,
profileKey: profileKey,
closedGroups: closedGroups,
openGroups: openGroups,
contacts: contacts
)
}
}

View File

@ -6,361 +6,80 @@ import SessionUtilitiesKit
public final class ConfigurationMessage: ControlMessage {
private enum CodingKeys: String, CodingKey {
case closedGroups
case openGroups
case displayName
case profilePictureUrl
case profileKey
case contacts
}
public var closedGroups: Set<CMClosedGroup> = []
public var openGroups: Set<String> = []
public var displayName: String?
public var profilePictureUrl: String?
public var profileKey: Data?
public var contacts: Set<CMContact> = []
public override var isSelfSendValid: Bool { true }
// MARK: - Initialization
public init(
displayName: String?,
profilePictureUrl: String?,
profileKey: Data?,
closedGroups: Set<CMClosedGroup>,
openGroups: Set<String>,
contacts: Set<CMContact>
profileKey: Data?
) {
super.init()
self.displayName = displayName
self.profilePictureUrl = profilePictureUrl
self.profileKey = profileKey
self.closedGroups = closedGroups
self.openGroups = openGroups
self.contacts = contacts
}
// MARK: - Codable
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
closedGroups = ((try? container.decode(Set<CMClosedGroup>.self, forKey: .closedGroups)) ?? [])
openGroups = ((try? container.decode(Set<String>.self, forKey: .openGroups)) ?? [])
displayName = try? container.decode(String.self, forKey: .displayName)
profilePictureUrl = try? container.decode(String.self, forKey: .profilePictureUrl)
profileKey = try? container.decode(Data.self, forKey: .profileKey)
contacts = ((try? container.decode(Set<CMContact>.self, forKey: .contacts)) ?? [])
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(closedGroups, forKey: .closedGroups)
try container.encodeIfPresent(openGroups, forKey: .openGroups)
try container.encodeIfPresent(displayName, forKey: .displayName)
try container.encodeIfPresent(profilePictureUrl, forKey: .profilePictureUrl)
try container.encodeIfPresent(profileKey, forKey: .profileKey)
try container.encodeIfPresent(contacts, forKey: .contacts)
}
// MARK: - Proto Conversion
public override class func fromProto(_ proto: SNProtoContent, sender: String) -> ConfigurationMessage? {
guard let configurationProto = proto.configurationMessage else { return nil }
let displayName = configurationProto.displayName
let profilePictureUrl = configurationProto.profilePicture
let profileKey = configurationProto.profileKey
let closedGroups = Set(configurationProto.closedGroups.compactMap { CMClosedGroup.fromProto($0) })
let openGroups = Set(configurationProto.openGroups)
let contacts = Set(configurationProto.contacts.compactMap { CMContact.fromProto($0) })
return ConfigurationMessage(
displayName: displayName,
profilePictureUrl: profilePictureUrl,
profileKey: profileKey,
closedGroups: closedGroups,
openGroups: openGroups,
contacts: contacts
profileKey: profileKey
)
}
public override func toProto(_ db: Database) -> SNProtoContent? {
let configurationProto = SNProtoConfigurationMessage.builder()
if let displayName = displayName { configurationProto.setDisplayName(displayName) }
if let profilePictureUrl = profilePictureUrl { configurationProto.setProfilePicture(profilePictureUrl) }
if let profileKey = profileKey { configurationProto.setProfileKey(profileKey) }
configurationProto.setClosedGroups(closedGroups.compactMap { $0.toProto() })
configurationProto.setOpenGroups([String](openGroups))
configurationProto.setContacts(contacts.compactMap { $0.toProto() })
let contentProto = SNProtoContent.builder()
do {
contentProto.setConfigurationMessage(try configurationProto.build())
return try contentProto.build()
} catch {
SNLog("Couldn't construct configuration proto from: \(self).")
return nil
}
}
public override func toProto(_ db: Database) -> SNProtoContent? { return nil }
// MARK: - Description
public var description: String {
"""
ConfigurationMessage(
closedGroups: \([CMClosedGroup](closedGroups).prettifiedDescription),
openGroups: \([String](openGroups).prettifiedDescription),
LegacyConfigurationMessage(
displayName: \(displayName ?? "null"),
profilePictureUrl: \(profilePictureUrl ?? "null"),
profileKey: \(profileKey?.toHexString() ?? "null"),
contacts: \([CMContact](contacts).prettifiedDescription)
profileKey: \(profileKey?.toHexString() ?? "null")
)
"""
}
}
// MARK: - Closed Group
extension ConfigurationMessage {
public struct CMClosedGroup: Codable, Hashable, CustomStringConvertible {
private enum CodingKeys: String, CodingKey {
case publicKey
case name
case encryptionKeyPublicKey
case encryptionKeySecretKey
case members
case admins
case expirationTimer
}
public let publicKey: String
public let name: String
public let encryptionKeyPublicKey: Data
public let encryptionKeySecretKey: Data
public let members: Set<String>
public let admins: Set<String>
public let expirationTimer: UInt32
public var isValid: Bool { !members.isEmpty && !admins.isEmpty }
// MARK: - Initialization
public init(
publicKey: String,
name: String,
encryptionKeyPublicKey: Data,
encryptionKeySecretKey: Data,
members: Set<String>,
admins: Set<String>,
expirationTimer: UInt32
) {
self.publicKey = publicKey
self.name = name
self.encryptionKeyPublicKey = encryptionKeyPublicKey
self.encryptionKeySecretKey = encryptionKeySecretKey
self.members = members
self.admins = admins
self.expirationTimer = expirationTimer
}
// MARK: - Codable
public init(from decoder: Decoder) throws {
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
publicKey = try container.decode(String.self, forKey: .publicKey)
name = try container.decode(String.self, forKey: .name)
encryptionKeyPublicKey = try container.decode(Data.self, forKey: .encryptionKeyPublicKey)
encryptionKeySecretKey = try container.decode(Data.self, forKey: .encryptionKeySecretKey)
members = try container.decode(Set<String>.self, forKey: .members)
admins = try container.decode(Set<String>.self, forKey: .admins)
expirationTimer = try container.decode(UInt32.self, forKey: .expirationTimer)
}
public func encode(to encoder: Encoder) throws {
var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self)
try container.encode(publicKey, forKey: .publicKey)
try container.encode(name, forKey: .name)
try container.encode(encryptionKeyPublicKey, forKey: .encryptionKeyPublicKey)
try container.encode(encryptionKeySecretKey, forKey: .encryptionKeySecretKey)
try container.encode(members, forKey: .members)
try container.encode(admins, forKey: .admins)
try container.encode(expirationTimer, forKey: .expirationTimer)
}
public static func fromProto(_ proto: SNProtoConfigurationMessageClosedGroup) -> CMClosedGroup? {
guard
let publicKey = proto.publicKey?.toHexString(),
let name = proto.name,
let encryptionKeyPairAsProto = proto.encryptionKeyPair
else { return nil }
let members = Set(proto.members.map { $0.toHexString() })
let admins = Set(proto.admins.map { $0.toHexString() })
let expirationTimer = proto.expirationTimer
let result = CMClosedGroup(
publicKey: publicKey,
name: name,
encryptionKeyPublicKey: encryptionKeyPairAsProto.publicKey,
encryptionKeySecretKey: encryptionKeyPairAsProto.privateKey,
members: members,
admins: admins,
expirationTimer: expirationTimer
)
guard result.isValid else { return nil }
return result
}
public func toProto() -> SNProtoConfigurationMessageClosedGroup? {
guard isValid else { return nil }
let result = SNProtoConfigurationMessageClosedGroup.builder()
result.setPublicKey(Data(hex: publicKey))
result.setName(name)
do {
let encryptionKeyPairAsProto = try SNProtoKeyPair.builder(
publicKey: encryptionKeyPublicKey,
privateKey: encryptionKeySecretKey
).build()
result.setEncryptionKeyPair(encryptionKeyPairAsProto)
} catch {
SNLog("Couldn't construct closed group proto from: \(self).")
return nil
}
result.setMembers(members.map { Data(hex: $0) })
result.setAdmins(admins.map { Data(hex: $0) })
result.setExpirationTimer(expirationTimer)
do {
return try result.build()
} catch {
SNLog("Couldn't construct closed group proto from: \(self).")
return nil
}
}
public var description: String { name }
}
}
// MARK: - Contact
extension ConfigurationMessage {
public struct CMContact: Codable, Hashable, CustomStringConvertible {
private enum CodingKeys: String, CodingKey {
case publicKey
case displayName
case profilePictureUrl
case profileKey
case hasIsApproved
case isApproved
case hasIsBlocked
case isBlocked
case hasDidApproveMe
case didApproveMe
}
public var publicKey: String?
public var displayName: String?
public var profilePictureUrl: String?
public var profileKey: Data?
public var hasIsApproved: Bool
public var isApproved: Bool
public var hasIsBlocked: Bool
public var isBlocked: Bool
public var hasDidApproveMe: Bool
public var didApproveMe: Bool
public var isValid: Bool { publicKey != nil && displayName != nil }
public init(
publicKey: String?,
displayName: String?,
profilePictureUrl: String?,
profileKey: Data?,
hasIsApproved: Bool,
isApproved: Bool,
hasIsBlocked: Bool,
isBlocked: Bool,
hasDidApproveMe: Bool,
didApproveMe: Bool
) {
self.publicKey = publicKey
self.displayName = displayName
self.profilePictureUrl = profilePictureUrl
self.profileKey = profileKey
self.hasIsApproved = hasIsApproved
self.isApproved = isApproved
self.hasIsBlocked = hasIsBlocked
self.isBlocked = isBlocked
self.hasDidApproveMe = hasDidApproveMe
self.didApproveMe = didApproveMe
}
// MARK: - Codable
public init(from decoder: Decoder) throws {
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
publicKey = try? container.decode(String.self, forKey: .publicKey)
displayName = try? container.decode(String.self, forKey: .displayName)
profilePictureUrl = try? container.decode(String.self, forKey: .profilePictureUrl)
profileKey = try? container.decode(Data.self, forKey: .profileKey)
hasIsApproved = try container.decode(Bool.self, forKey: .hasIsApproved)
isApproved = try container.decode(Bool.self, forKey: .isApproved)
hasIsBlocked = try container.decode(Bool.self, forKey: .hasIsBlocked)
isBlocked = try container.decode(Bool.self, forKey: .isBlocked)
hasDidApproveMe = try container.decode(Bool.self, forKey: .hasDidApproveMe)
didApproveMe = try container.decode(Bool.self, forKey: .didApproveMe)
}
public static func fromProto(_ proto: SNProtoConfigurationMessageContact) -> CMContact? {
let result: CMContact = CMContact(
publicKey: proto.publicKey.toHexString(),
displayName: proto.name,
profilePictureUrl: proto.profilePicture,
profileKey: proto.profileKey,
hasIsApproved: proto.hasIsApproved,
isApproved: proto.isApproved,
hasIsBlocked: proto.hasIsBlocked,
isBlocked: proto.isBlocked,
hasDidApproveMe: proto.hasDidApproveMe,
didApproveMe: proto.didApproveMe
)
guard result.isValid else { return nil }
return result
}
public func toProto() -> SNProtoConfigurationMessageContact? {
guard isValid else { return nil }
guard let publicKey = publicKey, let displayName = displayName else { return nil }
let result = SNProtoConfigurationMessageContact.builder(publicKey: Data(hex: publicKey), name: displayName)
if let profilePictureUrl = profilePictureUrl { result.setProfilePicture(profilePictureUrl) }
if let profileKey = profileKey { result.setProfileKey(profileKey) }
if hasIsApproved { result.setIsApproved(isApproved) }
if hasIsBlocked { result.setIsBlocked(isBlocked) }
if hasDidApproveMe { result.setDidApproveMe(didApproveMe) }
do {
return try result.build()
} catch {
SNLog("Couldn't construct contact proto from: \(self).")
return nil
}
}
public var description: String { displayName ?? "" }
}
}

View File

@ -1,235 +0,0 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import Sodium
import SessionUIKit
import SessionUtilitiesKit
extension MessageReceiver {
internal static func handleLegacyConfigurationMessage(
_ db: Database,
message: ConfigurationMessage,
using dependencies: Dependencies
) throws {
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
guard !SessionUtil.userConfigsEnabled(db) else {
TopBannerController.show(warning: .outdatedUserConfig)
return
}
let userPublicKey = getUserHexEncodedPublicKey(db)
guard message.sender == userPublicKey else { return }
SNLog("Configuration message received.")
// Note: `message.sentTimestamp` is in ms (convert to TimeInterval before converting to
// seconds to maintain the accuracy)
let isInitialSync: Bool = (!UserDefaults.standard[.hasSyncedInitialConfiguration])
let messageSentTimestamp: TimeInterval = TimeInterval((message.sentTimestamp ?? 0) / 1000)
let lastConfigTimestamp: TimeInterval = UserDefaults.standard[.lastConfigurationSync]
.defaulting(to: Date(timeIntervalSince1970: 0))
.timeIntervalSince1970
// Handle user profile changes
try ProfileManager.updateProfileIfNeeded(
db,
publicKey: userPublicKey,
name: message.displayName,
avatarUpdate: {
guard
let profilePictureUrl: String = message.profilePictureUrl,
let profileKey: Data = message.profileKey
else { return .none }
return .updateTo(
url: profilePictureUrl,
key: profileKey,
fileName: nil
)
}(),
sentTimestamp: messageSentTimestamp,
calledFromConfigHandling: true,
using: dependencies
)
// Create a contact for the current user if needed (also force-approve the current user
// in case the account got into a weird state or restored directly from a migration)
let userContact: Contact = Contact.fetchOrCreate(db, id: userPublicKey)
if !userContact.isTrusted || !userContact.isApproved || !userContact.didApproveMe {
try userContact.save(db)
try Contact
.filter(id: userPublicKey)
.updateAll( // Handling a config update so don't use `updateAllAndConfig`
db,
Contact.Columns.isTrusted.set(to: true),
Contact.Columns.isApproved.set(to: true),
Contact.Columns.didApproveMe.set(to: true)
)
}
if isInitialSync || messageSentTimestamp > lastConfigTimestamp {
if isInitialSync {
UserDefaults.standard[.hasSyncedInitialConfiguration] = true
NotificationCenter.default.post(name: .initialConfigurationMessageReceived, object: nil)
}
UserDefaults.standard[.lastConfigurationSync] = Date(timeIntervalSince1970: messageSentTimestamp)
// Contacts
try message.contacts.forEach { contactInfo in
guard let sessionId: String = contactInfo.publicKey else { return }
// If the contact is a blinded contact then only add them if they haven't already been
// unblinded
if SessionId.Prefix(from: sessionId) == .blinded15 || SessionId.Prefix(from: sessionId) == .blinded25 {
let hasUnblindedContact: Bool = BlindedIdLookup
.filter(BlindedIdLookup.Columns.blindedId == sessionId)
.filter(BlindedIdLookup.Columns.sessionId != nil)
.isNotEmpty(db)
if hasUnblindedContact {
return
}
}
// Note: We only update the contact and profile records if the data has actually changed
// in order to avoid triggering UI updates for every thread on the home screen
let contact: Contact = Contact.fetchOrCreate(db, id: sessionId)
let profile: Profile = Profile.fetchOrCreate(db, id: sessionId)
if
profile.name != contactInfo.displayName ||
profile.profilePictureUrl != contactInfo.profilePictureUrl ||
profile.profileEncryptionKey != contactInfo.profileKey
{
try profile.save(db)
try Profile
.filter(id: sessionId)
.updateAll( // Handling a config update so don't use `updateAllAndConfig`
db,
[
Profile.Columns.name.set(to: contactInfo.displayName),
(contactInfo.profilePictureUrl == nil ? nil :
Profile.Columns.profilePictureUrl.set(to: contactInfo.profilePictureUrl)
),
(contactInfo.profileKey == nil ? nil :
Profile.Columns.profileEncryptionKey.set(to: contactInfo.profileKey)
)
].compactMap { $0 }
)
}
/// We only update these values if the proto actually has values for them (this is to prevent an
/// edge case where an old client could override the values with default values since they aren't included)
///
/// **Note:** Since message requests have no reverse, we should only handle setting `isApproved`
/// and `didApproveMe` to `true`. This may prevent some weird edge cases where a config message
/// swapping `isApproved` and `didApproveMe` to `false`
if
(contactInfo.hasIsApproved && (contact.isApproved != contactInfo.isApproved)) ||
(contactInfo.hasIsBlocked && (contact.isBlocked != contactInfo.isBlocked)) ||
(contactInfo.hasDidApproveMe && (contact.didApproveMe != contactInfo.didApproveMe))
{
try contact.save(db)
try Contact
.filter(id: sessionId)
.updateAll( // Handling a config update so don't use `updateAllAndConfig`
db,
[
(!contactInfo.hasIsApproved || !contactInfo.isApproved ? nil :
Contact.Columns.isApproved.set(to: true)
),
(!contactInfo.hasIsBlocked ? nil :
Contact.Columns.isBlocked.set(to: contactInfo.isBlocked)
),
(!contactInfo.hasDidApproveMe || !contactInfo.didApproveMe ? nil :
Contact.Columns.didApproveMe.set(to: contactInfo.didApproveMe)
)
].compactMap { $0 }
)
}
// If the contact is blocked
if contactInfo.hasIsBlocked && contactInfo.isBlocked {
// If this message changed them to the blocked state and there is an existing thread
// associated with them that is a message request thread then delete it (assume
// that the current user had deleted that message request)
if
contactInfo.isBlocked != contact.isBlocked, // 'contact.isBlocked' will be the old value
let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId),
thread.isMessageRequest(db)
{
_ = try thread.delete(db)
}
}
}
// Closed groups
//
// Note: Only want to add these for initial sync to avoid re-adding closed groups the user
// intentionally left (any closed groups joined since the first processed sync message should
// get added via the 'handleNewClosedGroup' method anyway as they will have come through in the
// past two weeks)
if isInitialSync {
let existingClosedGroupsIds: [String] = (try? SessionThread
.filter(SessionThread.Columns.variant == SessionThread.Variant.legacyGroup)
.fetchAll(db))
.defaulting(to: [])
.map { $0.id }
try message.closedGroups.forEach { closedGroup in
guard !existingClosedGroupsIds.contains(closedGroup.publicKey) else { return }
let keyPair: KeyPair = KeyPair(
publicKey: closedGroup.encryptionKeyPublicKey.bytes,
secretKey: closedGroup.encryptionKeySecretKey.bytes
)
try MessageReceiver.handleNewClosedGroup(
db,
groupPublicKey: closedGroup.publicKey,
name: closedGroup.name,
encryptionKeyPair: keyPair,
members: [String](closedGroup.members),
admins: [String](closedGroup.admins),
expirationTimer: closedGroup.expirationTimer,
formationTimestampMs: message.sentTimestamp!,
calledFromConfigHandling: false, // Legacy config isn't an issue
using: dependencies
)
}
}
// Open groups
for openGroupURL in message.openGroups {
if let (room, server, publicKey) = SessionUtil.parseCommunity(url: openGroupURL) {
let successfullyAddedGroup: Bool = OpenGroupManager.shared
.add(
db,
roomToken: room,
server: server,
publicKey: publicKey,
calledFromConfigHandling: true
)
if successfullyAddedGroup {
db.afterNextTransactionNested { _ in
OpenGroupManager.shared.performInitialRequestsAfterAdd(
successfullyAddedGroup: successfullyAddedGroup,
roomToken: room,
server: server,
publicKey: publicKey,
calledFromConfigHandling: false
)
.subscribe(on: OpenGroupAPI.workQueue)
.sinkUntilComplete()
}
}
}
}
}
}
}

View File

@ -3,6 +3,7 @@
import Foundation
import GRDB
import Sodium
import SessionUIKit
import SessionUtilitiesKit
import SessionSnodeKit
@ -242,13 +243,6 @@ public enum MessageReceiver {
message: message
)
case let message as ConfigurationMessage:
try MessageReceiver.handleLegacyConfigurationMessage(
db,
message: message,
using: dependencies
)
case let message as UnsendRequest:
try MessageReceiver.handleUnsendRequest(
db,
@ -282,6 +276,7 @@ public enum MessageReceiver {
)
// SharedConfigMessages should be handled by the 'SharedUtil' instead of this
case is ConfigurationMessage: TopBannerController.show(warning: .outdatedUserConfig)
case is SharedConfigMessage: throw MessageReceiverError.invalidSharedConfigMessageHandling
default: fatalError()

View File

@ -3,18 +3,9 @@
import Foundation
public extension Notification.Name {
// FIXME: Remove once `useSharedUtilForUserConfig` is permanent
static let initialConfigurationMessageReceived = Notification.Name("initialConfigurationMessageReceived")
static let missedCall = Notification.Name("missedCall")
}
public extension Notification.Key {
static let senderId = Notification.Key("senderId")
}
@objc public extension NSNotification {
// FIXME: Remove once `useSharedUtilForUserConfig` is permanent
@objc static let initialConfigurationMessageReceived = Notification.Name.initialConfigurationMessageReceived.rawValue as NSString
}

View File

@ -14,12 +14,7 @@ public final class CurrentUserPoller: Poller {
// MARK: - Settings
override var namespaces: [SnodeAPI.Namespace] {
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
guard SessionUtil.userConfigsEnabled else { return [.default] }
return CurrentUserPoller.namespaces
}
override var namespaces: [SnodeAPI.Namespace] { CurrentUserPoller.namespaces }
/// After polling a given snode this many times we always switch to a new one.
///

View File

@ -50,9 +50,6 @@ internal extension SessionUtil {
publicKey: String,
change: (UnsafeMutablePointer<config_object>?) throws -> ()
) throws {
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
guard SessionUtil.userConfigsEnabled(db, ignoreRequirementsForRunningMigrations: true) else { return }
// Since we are doing direct memory manipulation we are using an `Atomic`
// type which has blocking access in it's `mutate` closure
let needsPush: Bool
@ -307,9 +304,6 @@ internal extension SessionUtil {
targetConfig: ConfigDump.Variant,
changeTimestampMs: Int64
) -> Bool {
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
guard SessionUtil.userConfigsEnabled(db) else { return true }
let targetPublicKey: String = {
switch targetConfig {
default: return getUserHexEncodedPublicKey(db)
@ -349,10 +343,7 @@ public extension SessionUtil {
threadVariant: SessionThread.Variant,
visibleOnly: Bool
) -> Bool {
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
guard SessionUtil.userConfigsEnabled(db) else { return true }
let userPublicKey: String = getUserHexEncodedPublicKey()
let userPublicKey: String = getUserHexEncodedPublicKey(db)
let configVariant: ConfigDump.Variant = {
switch threadVariant {
case .contact: return (threadId == userPublicKey ? .userProfile : .contacts)

View File

@ -91,11 +91,7 @@ public extension QueryInterfaceRequest where RowDecoder: FetchableRecord & Table
let updatedData: [RowDecoder] = try self.updateAndFetchAll(db, assignments.map { $0.assignment })
// Then check if any of the changes could affect the config
guard
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
SessionUtil.userConfigsEnabled(db, ignoreRequirementsForRunningMigrations: true),
SessionUtil.assignmentsRequireConfigUpdate(assignments)
else { return updatedData }
guard SessionUtil.assignmentsRequireConfigUpdate(assignments) else { return updatedData }
defer {
// If we changed a column that requires a config update then we may as well automatically

View File

@ -6,25 +6,6 @@ import SessionSnodeKit
import SessionUtil
import SessionUtilitiesKit
// MARK: - Features
public extension Features {
static func useSharedUtilForUserConfig(_ db: Database? = nil) -> Bool {
guard Date().timeIntervalSince1970 < 1690761600 else { return true }
guard !SessionUtil.hasCheckedMigrationsCompleted.wrappedValue else {
return SessionUtil.userConfigsEnabledIgnoringFeatureFlag
}
if let db: Database = db {
return SessionUtil.refreshingUserConfigsEnabled(db)
}
return Storage.shared
.read { db in SessionUtil.refreshingUserConfigsEnabled(db) }
.defaulting(to: false)
}
}
// MARK: - SessionUtil
public enum SessionUtil {
@ -70,10 +51,7 @@ public enum SessionUtil {
/// Returns `true` if there is a config which needs to be pushed, but returns `false` if the configs are all up to date or haven't been
/// loaded yet (eg. fresh install)
public static var needsSync: Bool {
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
guard SessionUtil.userConfigsEnabled else { return false }
return configStore
configStore
.wrappedValue
.contains { _, atomicConf in
guard atomicConf.wrappedValue != nil else { return false }
@ -84,56 +62,6 @@ public enum SessionUtil {
public static var libSessionVersion: String { String(cString: LIBSESSION_UTIL_VERSION_STR) }
fileprivate static let hasCheckedMigrationsCompleted: Atomic<Bool> = Atomic(false)
private static let requiredMigrationsCompleted: Atomic<Bool> = Atomic(false)
private static let requiredMigrationIdentifiers: Set<String> = [
TargetMigrations.Identifier.messagingKit.key(with: _013_SessionUtilChanges.self),
TargetMigrations.Identifier.messagingKit.key(with: _014_GenerateInitialUserConfigDumps.self)
]
public static var userConfigsEnabled: Bool {
return userConfigsEnabled(nil)
}
public static func userConfigsEnabled(_ db: Database?) -> Bool {
Features.useSharedUtilForUserConfig(db) &&
SessionUtil.userConfigsEnabledIgnoringFeatureFlag
}
public static var userConfigsEnabledIgnoringFeatureFlag: Bool {
SessionUtil.requiredMigrationsCompleted.wrappedValue
}
internal static func userConfigsEnabled(
_ db: Database,
ignoreRequirementsForRunningMigrations: Bool
) -> Bool {
// First check if we are enabled regardless of what we want to ignore
guard
Features.useSharedUtilForUserConfig(db),
!SessionUtil.requiredMigrationsCompleted.wrappedValue,
!SessionUtil.refreshingUserConfigsEnabled(db),
ignoreRequirementsForRunningMigrations,
let currentlyRunningMigration: (identifier: TargetMigrations.Identifier, migration: Migration.Type) = Storage.shared.currentlyRunningMigration
else { return true }
let nonIgnoredMigrationIdentifiers: Set<String> = SessionUtil.requiredMigrationIdentifiers
.removing(currentlyRunningMigration.identifier.key(with: currentlyRunningMigration.migration))
return Storage.appliedMigrationIdentifiers(db)
.isSuperset(of: nonIgnoredMigrationIdentifiers)
}
@discardableResult public static func refreshingUserConfigsEnabled(_ db: Database) -> Bool {
let result: Bool = Storage.appliedMigrationIdentifiers(db)
.isSuperset(of: SessionUtil.requiredMigrationIdentifiers)
requiredMigrationsCompleted.mutate { $0 = result }
hasCheckedMigrationsCompleted.mutate { $0 = true }
return result
}
internal static func lastError(_ conf: UnsafeMutablePointer<config_object>?) -> String {
return (conf?.pointee.last_error.map { String(cString: $0) } ?? "Unknown")
}
@ -141,9 +69,6 @@ public enum SessionUtil {
// MARK: - Loading
public static func clearMemoryState() {
// Ensure we have a loaded state before we continue
guard !SessionUtil.configStore.wrappedValue.isEmpty else { return }
SessionUtil.configStore.mutate { confStore in
confStore.removeAll()
}
@ -169,9 +94,6 @@ public enum SessionUtil {
return
}
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
guard SessionUtil.userConfigsEnabled(db, ignoreRequirementsForRunningMigrations: true) else { return }
// Retrieve the existing dumps from the database
let existingDumps: Set<ConfigDump> = ((try? ConfigDump.fetchSet(db)) ?? [])
let existingDumpVariants: Set<ConfigDump.Variant> = existingDumps
@ -395,9 +317,6 @@ public enum SessionUtil {
}
public static func configHashes(for publicKey: String) -> [String] {
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
guard SessionUtil.userConfigsEnabled else { return [] }
return Storage.shared
.read { db -> Set<ConfigDump.Variant> in
guard Identity.userExists(db) else { return [] }
@ -437,8 +356,6 @@ public enum SessionUtil {
messages: [SharedConfigMessage],
publicKey: String
) throws {
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
guard SessionUtil.userConfigsEnabled(db) else { return }
guard !messages.isEmpty else { return }
guard !publicKey.isEmpty else { throw MessageReceiverError.noThread }

View File

@ -509,8 +509,7 @@ public struct ProfileManager {
// Name
if let name: String = name, !name.isEmpty, name != profile.name {
// FIXME: Remove the `userConfigsEnabled` check once `useSharedUtilForUserConfig` is permanent
if sentTimestamp > profile.lastNameUpdate || (isCurrentUser && (calledFromConfigHandling || !SessionUtil.userConfigsEnabled(db))) {
if sentTimestamp > profile.lastNameUpdate || (isCurrentUser && calledFromConfigHandling) {
profileChanges.append(Profile.Columns.name.set(to: name))
profileChanges.append(Profile.Columns.lastNameUpdate.set(to: sentTimestamp))
}
@ -520,8 +519,7 @@ public struct ProfileManager {
var avatarNeedsDownload: Bool = false
var targetAvatarUrl: String? = nil
// FIXME: Remove the `userConfigsEnabled` check once `useSharedUtilForUserConfig` is permanent
if sentTimestamp > profile.lastProfilePictureUpdate || (isCurrentUser && (calledFromConfigHandling || !SessionUtil.userConfigsEnabled(db))) {
if sentTimestamp > profile.lastProfilePictureUpdate || (isCurrentUser && calledFromConfigHandling) {
switch avatarUpdate {
case .none: break
case .uploadImageData: preconditionFailure("Invalid options for this function")
@ -571,25 +569,6 @@ public struct ProfileManager {
profileChanges
)
}
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
else if !SessionUtil.userConfigsEnabled(db) {
// If we have a contact record for the profile (ie. it's a synced profile) then
// should should send an updated config message, otherwise we should just update
// the local state (the shared util has this logic build in to it's handling)
if (try? Contact.exists(db, id: publicKey)) == true {
try Profile
.filter(id: publicKey)
.updateAllAndConfig(db, profileChanges)
}
else {
try Profile
.filter(id: publicKey)
.updateAll(
db,
profileChanges
)
}
}
else {
try Profile
.filter(id: publicKey)

View File

@ -37,7 +37,6 @@ public enum SNUserDefaults {
}
public enum Date: Swift.String {
case lastConfigurationSync
case lastProfilePictureUpload
case lastOpenGroupImageUpdate
case lastOpen

View File

@ -84,12 +84,6 @@ public enum AppSetup {
)
}
// Refresh the migration state for 'SessionUtil' so it's logic can start running
// correctly when called (doing this here instead of automatically via the
// `SessionUtil.userConfigsEnabled` property to avoid having to use the correct
// method when calling within a database read/write closure)
Storage.shared.read { db in SessionUtil.refreshingUserConfigsEnabled(db) }
migrationsCompletion(result, (needsConfigSync || SessionUtil.needsSync))
// The 'if' is only there to prevent the "variable never read" warning from showing