From 8e04944af03c22a3b4a0c190d295761d08544ab0 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 12 Oct 2023 17:31:53 +1100 Subject: [PATCH] Fixes from cross-platform testing and general code changes Fixed the incorrect Group Namespaces Fixed an incorrect identity generation which could create invalid accounts Fixed an issue where adding group members would remove admins incorrectly Finished updating the SnodeAPI to use prepared requests --- Scripts/LintLocalizableStrings.swift | 2 +- Session.xcodeproj/project.pbxproj | 10 +- Session/Closed Groups/EditClosedGroupVC.swift | 7 +- .../ConversationVC+Interaction.swift | 31 ++- ...isappearingMessagesSettingsViewModel.swift | 2 +- Session/Home/HomeViewModel.swift | 7 - .../MessageRequestsViewModel.swift | 7 +- .../PhotoCollectionPickerViewModel.swift | 2 +- .../Settings/BlockedContactsViewModel.swift | 2 +- .../ConversationSettingsViewModel.swift | 12 - .../NotificationSettingsViewModel.swift | 14 +- .../Settings/NotificationSoundViewModel.swift | 2 - Session/Settings/SettingsViewModel.swift | 45 ++-- .../Shared/Types/ObservableTableSource.swift | 4 +- .../Shared/Types/PagedObservationSource.swift | 2 +- .../Database/Models/ClosedGroup.swift | 2 +- .../Jobs/Types/ExpirationUpdateJob.swift | 20 +- .../Jobs/Types/GetExpirationJob.swift | 32 +-- .../MessageReceiver+UnsendRequests.swift | 15 +- .../MessageSender+Groups.swift | 21 +- .../SessionUtil+GroupMembers.swift | 45 ++++ ...ddSnodeReveivedMessageInfoPrimaryKey.swift | 4 +- .../Models/SnodeReceivedMessageInfo.swift | 27 +- .../Models/GetMessagesResponse.swift | 4 +- .../Models/SnodeReceivedMessage.swift | 2 +- SessionSnodeKit/Networking/SnodeAPI.swift | 235 ++++-------------- SessionSnodeKit/Types/SnodeAPINamespace.swift | 32 ++- .../NotificationContentViewModelSpec.swift | 2 +- .../Database/Models/Identity.swift | 2 +- .../Networking/PreparedRequest.swift | 1 - 30 files changed, 241 insertions(+), 352 deletions(-) diff --git a/Scripts/LintLocalizableStrings.swift b/Scripts/LintLocalizableStrings.swift index 03a5117de..1f966b304 100755 --- a/Scripts/LintLocalizableStrings.swift +++ b/Scripts/LintLocalizableStrings.swift @@ -79,7 +79,7 @@ extension ProjectState { .regex("case .* = "), .regex("Error.*\\("), .regex("Crypto.*\\(id:"), - .containsAnd("id:", .previousLine(numEarlier: 1, .regex("Crypto.*\\("))) + .containsAnd("id:", caseSensitive: false, .previousLine(numEarlier: 1, .regex("Crypto.*\\("))) ] } diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 1ad0a6d7c..690b1504f 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -529,7 +529,6 @@ FD1D732E2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */; }; FD1F9C9F2A862BE60050F671 /* MigrationRequirement.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1F9C9E2A862BE60050F671 /* MigrationRequirement.swift */; }; FD23CE1B2A651E6D0000B97C /* NetworkType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1A2A651E6D0000B97C /* NetworkType.swift */; }; - FD23CE1F2A65269C0000B97C /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1E2A65269C0000B97C /* Crypto.swift */; }; FD23CE222A661D000000B97C /* Crypto+OpenGroupAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE212A661D000000B97C /* Crypto+OpenGroupAPI.swift */; }; FD23CE242A675C440000B97C /* Crypto+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE232A675C440000B97C /* Crypto+SessionMessagingKit.swift */; }; FD23CE262A676B5B0000B97C /* DependenciesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE252A676B5B0000B97C /* DependenciesSpec.swift */; }; @@ -589,6 +588,7 @@ FD30036E2A3AE26000B5A5FB /* CExceptionHelper.mm in Sources */ = {isa = PBXBuildFile; fileRef = FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */; }; FD368A6829DE8F9C000DBF1E /* _012_AddFTSIfNeeded.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */; }; FD368A6A29DE9E30000DBF1E /* UIContextualAction+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD368A6929DE9E30000DBF1E /* UIContextualAction+Utilities.swift */; }; + FD3765DA2AD7C91E00DC1489 /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765D92AD7C91D00DC1489 /* Crypto.swift */; }; FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */; }; FD37E9C628A1D4EC003AE748 /* Theme+ClassicDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */; }; FD37E9C828A1D73F003AE748 /* Theme+Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */; }; @@ -1765,7 +1765,6 @@ FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _015_BlockCommunityMessageRequests.swift; sourceTree = ""; }; FD1F9C9E2A862BE60050F671 /* MigrationRequirement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationRequirement.swift; sourceTree = ""; }; FD23CE1A2A651E6D0000B97C /* NetworkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkType.swift; sourceTree = ""; }; - FD23CE1E2A65269C0000B97C /* Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Crypto.swift; sourceTree = ""; }; FD23CE212A661D000000B97C /* Crypto+OpenGroupAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+OpenGroupAPI.swift"; sourceTree = ""; }; FD23CE232A675C440000B97C /* Crypto+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+SessionMessagingKit.swift"; sourceTree = ""; }; FD23CE252A676B5B0000B97C /* DependenciesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependenciesSpec.swift; sourceTree = ""; }; @@ -1787,6 +1786,7 @@ FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CExceptionHelper.mm; sourceTree = ""; }; FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _012_AddFTSIfNeeded.swift; sourceTree = ""; }; FD368A6929DE9E30000DBF1E /* UIContextualAction+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Utilities.swift"; sourceTree = ""; }; + FD3765D92AD7C91D00DC1489 /* Crypto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Crypto.swift; sourceTree = ""; }; FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+ClassicDark.swift"; sourceTree = ""; }; FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Colors.swift"; sourceTree = ""; }; @@ -2729,7 +2729,7 @@ B8A582AC258C653C00AFD84C /* Crypto */ = { isa = PBXGroup; children = ( - FD23CE1E2A65269C0000B97C /* Crypto.swift */, + FD3765D92AD7C91D00DC1489 /* Crypto.swift */, FD9AECA62AAAF5B0009B3406 /* Crypto+SessionUtilitiesKit.swift */, FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */, B88FA7FA26114EA70049422F /* Hex.swift */, @@ -3745,7 +3745,6 @@ FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */, FD3003692A3ADD6000B5A5FB /* CExceptionHelper.h */, FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */, - FD23CE1E2A65269C0000B97C /* Crypto.swift */, FD12A84A2AD6458800EEBA0D /* DifferenceKit+Utilities.swift */, FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */, FD09796A27F6C67500936362 /* Failable.swift */, @@ -6136,6 +6135,7 @@ FDFBB74B2A1EFF4900CA7350 /* Bencode.swift in Sources */, FDE519F72AB7CDC700450C53 /* Result+Utilities.swift in Sources */, FD5931A72A8DA5DA0040147D /* SQLInterpolation+Utilities.swift in Sources */, + FD3765DA2AD7C91E00DC1489 /* Crypto.swift in Sources */, FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */, FDF8487929405906007DCAE5 /* HTTPQueryParam.swift in Sources */, FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */, @@ -6148,7 +6148,6 @@ FD9AECA72AAAF5B0009B3406 /* Crypto+SessionUtilitiesKit.swift in Sources */, C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */, FD12A84B2AD6458800EEBA0D /* DifferenceKit+Utilities.swift in Sources */, - C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */, FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */, FD5931AB2A8DCB0A0040147D /* ScopeAdapter+Utilities.swift in Sources */, FD37E9FF28A5F2CD003AE748 /* Configuration.swift in Sources */, @@ -6181,7 +6180,6 @@ C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */, B8F5F58325EC94A6003BF8D4 /* Collection+Utilities.swift in Sources */, 7BD477A827EC39F5004E2822 /* Atomic.swift in Sources */, - FD23CE1F2A65269C0000B97C /* Crypto.swift in Sources */, B8BC00C0257D90E30032E807 /* General.swift in Sources */, FDF8488629405A61007DCAE5 /* Request.swift in Sources */, FD23CE302A67B8820000B97C /* CacheConfig.swift in Sources */, diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index 4900897da..cf02f307c 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -361,6 +361,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat let title: String = "vc_conversation_settings_invite_button_title".localized() let userSessionId: SessionId = self.userSessionId + let threadVariant: SessionThread.Variant = self.threadVariant let userSelectionVC: UserSelectionVC = UserSelectionVC( with: title, excluding: allGroupMembers @@ -405,7 +406,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat return (lhsDisplayName < rhsDisplayName) }) - .filter { $0.role == .standard || $0.role == .zombie } + .filter { $0.role != .zombie } let uniqueGroupMemberIds: Set = (self?.allGroupMembers ?? []) .map { $0.profileId } @@ -442,8 +443,8 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat let threadId: String = self.threadId let updatedName: String = self.name let userSessionId: SessionId = self.userSessionId - let updatedMembers: [(String, Profile?)] = self.allGroupMembers - .map { ($0.profileId, $0.profile) } + let updatedMembers: [(id: String, profile: Profile?, isAdmin: Bool)] = self.allGroupMembers + .map { ($0.profileId, $0.profile, ($0.role == .admin)) } let updatedMemberIds: Set = updatedMembers.map { $0.0 }.asSet() guard updatedMemberIds != self.originalMembersIds || updatedName != self.originalName else { diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 8debe2694..56663fda9 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -1839,15 +1839,14 @@ extension ConversationVC: func delete(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) { switch cellViewModel.variant { - case .standardIncomingDeleted, .infoCall, - .infoScreenshotNotification, .infoMediaSavedNotification, - .infoClosedGroupCreated, .infoClosedGroupUpdated, - .infoClosedGroupCurrentUserLeft, .infoClosedGroupCurrentUserLeaving, .infoClosedGroupCurrentUserErrorLeaving, + case .standardIncomingDeleted, .infoCall, .infoScreenshotNotification, .infoMediaSavedNotification, + .infoClosedGroupCreated, .infoClosedGroupUpdated, .infoClosedGroupCurrentUserLeft, + .infoClosedGroupCurrentUserLeaving, .infoClosedGroupCurrentUserErrorLeaving, .infoMessageRequestAccepted, .infoDisappearingMessagesUpdate: // Info messages and unsent messages should just trigger a local // deletion (they are created as side effects so we wouldn't be // able to delete them for all participants anyway) - Dependencies()[singleton: .storage].writeAsync { db in + dependencies[singleton: .storage].writeAsync { db in _ = try Interaction .filter(id: cellViewModel.id) .deleteAll(db) @@ -1858,8 +1857,8 @@ extension ConversationVC: } let threadName: String = self.viewModel.threadData.displayName - let userSessionId: SessionId = getUserSessionId(using: dependencies) + // Remote deletion logic func deleteRemotely(from viewController: UIViewController?, request: AnyPublisher, onComplete: (() -> ())?) { // Show a loading indicator @@ -2105,19 +2104,19 @@ extension ConversationVC: from: self, request: dependencies[singleton: .storage] .readPublisher(using: dependencies) { db in - try SnodeAPI.AuthenticationInfo( - db, - sessionIdHexString: targetPublicKey, - using: dependencies - ) - } - .flatMap { authInfo in - SnodeAPI - .deleteMessages( + try SnodeAPI + .preparedDeleteMessages( serverHashes: [serverHash], - authInfo: authInfo + requireSuccessfulDeletion: false, + authInfo: try SnodeAPI.AuthenticationInfo( + db, + sessionIdHexString: targetPublicKey, + using: dependencies + ), + using: dependencies ) } + .flatMap { $0.send(using: dependencies) } .map { _ in () } .eraseToAnyPublisher() ) { completeServerDeletion() } diff --git a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift index 1d7d20444..1e32ad0b1 100644 --- a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift @@ -513,7 +513,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel, Naviga } // Contacts & legacy closed groups need to update the SessionUtil - dependencies[singleton: .storage].writeAsync(using: dependencies) { [threadId, threadVariant] db in + dependencies[singleton: .storage].writeAsync(using: dependencies) { [threadId, threadVariant, dependencies] db in switch threadVariant { case .contact: try SessionUtil diff --git a/Session/Home/HomeViewModel.swift b/Session/Home/HomeViewModel.swift index 2f6322897..7e66addee 100644 --- a/Session/Home/HomeViewModel.swift +++ b/Session/Home/HomeViewModel.swift @@ -37,13 +37,6 @@ public class HomeViewModel { init( using dependencies: Dependencies = Dependencies() ) { - typealias InitialData = ( - userSessionId: SessionId, - showViewedSeedBanner: Bool, - hasHiddenMessageRequests: Bool, - profile: Profile - ) - let initialState: State? = dependencies[singleton: .storage].read { db -> State in try HomeViewModel.retrieveState(db, excludingMessageRequestThreadCount: true, using: dependencies) } diff --git a/Session/Home/Message Requests/MessageRequestsViewModel.swift b/Session/Home/Message Requests/MessageRequestsViewModel.swift index 01063cb8c..84916b7f3 100644 --- a/Session/Home/Message Requests/MessageRequestsViewModel.swift +++ b/Session/Home/Message Requests/MessageRequestsViewModel.swift @@ -33,6 +33,7 @@ class MessageRequestsViewModel: SessionTableViewModel, NavigatableStateHolder, O // distinct stutter) let userSessionId: SessionId = getUserSessionId(using: dependencies) let thread: TypedTableAlias = TypedTableAlias() + self.pagedDataObserver = PagedDatabaseObserver( pagedTable: SessionThread.self, pageSize: MessageRequestsViewModel.pageSize, @@ -225,7 +226,8 @@ class MessageRequestsViewModel: SessionTableViewModel, NavigatableStateHolder, O .map { id, _ in id }, threadVariant: .contact, groupLeaveType: .silent, - calledFromConfigHandling: false + calledFromConfigHandling: false, + using: dependencies ) // Remove the group requests @@ -236,7 +238,8 @@ class MessageRequestsViewModel: SessionTableViewModel, NavigatableStateHolder, O .map { id, _ in id }, threadVariant: .group, groupLeaveType: .silent, - calledFromConfigHandling: false + calledFromConfigHandling: false, + using: dependencies ) } } diff --git a/Session/Media Viewing & Editing/PhotoCollectionPickerViewModel.swift b/Session/Media Viewing & Editing/PhotoCollectionPickerViewModel.swift index e5c461e5b..7a0940565 100644 --- a/Session/Media Viewing & Editing/PhotoCollectionPickerViewModel.swift +++ b/Session/Media Viewing & Editing/PhotoCollectionPickerViewModel.swift @@ -41,7 +41,7 @@ class PhotoCollectionPickerViewModel: SessionTableViewModel, ObservableTableSour // MARK: - Content let title: String = "NOTIFICATIONS_STYLE_SOUND_TITLE".localized() - + lazy var observation: TargetObservation = ObservationBuilder .subject(photoCollections) .map { collections -> [SectionModel] in diff --git a/Session/Settings/BlockedContactsViewModel.swift b/Session/Settings/BlockedContactsViewModel.swift index d60e2a21b..5ee364e41 100644 --- a/Session/Settings/BlockedContactsViewModel.swift +++ b/Session/Settings/BlockedContactsViewModel.swift @@ -170,7 +170,7 @@ public class BlockedContactsViewModel: SessionTableViewModel, NavigatableStateHo let contactNames: [String] = contactIds .compactMap { contactId in guard - let section: BlockedContactsViewModel.SectionModel = self.tableData + let section: SectionModel = self.tableData .first(where: { section in section.model == .contacts }), let info: SessionCell.Info = section.elements .first(where: { info in info.id.id == contactId }) diff --git a/Session/Settings/ConversationSettingsViewModel.swift b/Session/Settings/ConversationSettingsViewModel.swift index fdf3da3fa..2e57b2aac 100644 --- a/Session/Settings/ConversationSettingsViewModel.swift +++ b/Session/Settings/ConversationSettingsViewModel.swift @@ -44,18 +44,6 @@ class ConversationSettingsViewModel: SessionTableViewModel, NavigatableStateHold } } - private let dependencies: Dependencies - - // MARK: - Initialization - - init( - using dependencies: Dependencies = Dependencies() - ) { - self.dependencies = dependencies - - super.init() - } - // MARK: - Content private struct State: Equatable { diff --git a/Session/Settings/NotificationSettingsViewModel.swift b/Session/Settings/NotificationSettingsViewModel.swift index 03aa90b47..1333fc555 100644 --- a/Session/Settings/NotificationSettingsViewModel.swift +++ b/Session/Settings/NotificationSettingsViewModel.swift @@ -50,18 +50,6 @@ class NotificationSettingsViewModel: SessionTableViewModel, NavigatableStateHold case content } - private let dependencies: Dependencies - - // MARK: - Initialization - - init( - using dependencies: Dependencies = Dependencies() - ) { - self.dependencies = dependencies - - super.init() - } - // MARK: - Content private struct State: Equatable { @@ -84,7 +72,7 @@ class NotificationSettingsViewModel: SessionTableViewModel, NavigatableStateHold .defaulting(to: Preferences.NotificationPreviewType.defaultPreviewType) ) } - .map { dbState -> State in + .map { [dependencies] dbState -> State in State( isUsingFullAPNs: dependencies[defaults: .standard, key: .isUsingFullAPNs], notificationSound: dbState.notificationSound, diff --git a/Session/Settings/NotificationSoundViewModel.swift b/Session/Settings/NotificationSoundViewModel.swift index abeddb323..89d53fdbe 100644 --- a/Session/Settings/NotificationSoundViewModel.swift +++ b/Session/Settings/NotificationSoundViewModel.swift @@ -18,7 +18,6 @@ class NotificationSoundViewModel: SessionTableViewModel, NavigationItemSource, N // FIXME: Remove `threadId` once we ditch the per-thread notification sound private let threadId: String? - private let dependencies: Dependencies private var audioPlayer: OWSAudioPlayer? private var storedSelection: Preferences.Sound? private var currentSelection: CurrentValueSubject = CurrentValueSubject(nil) @@ -28,7 +27,6 @@ class NotificationSoundViewModel: SessionTableViewModel, NavigationItemSource, N init(threadId: String? = nil, using dependencies: Dependencies = Dependencies()) { self.dependencies = dependencies self.threadId = threadId - self.dependencies = dependencies } deinit { diff --git a/Session/Settings/SettingsViewModel.swift b/Session/Settings/SettingsViewModel.swift index 089df13e9..bca96abaa 100644 --- a/Session/Settings/SettingsViewModel.swift +++ b/Session/Settings/SettingsViewModel.swift @@ -97,27 +97,25 @@ class SettingsViewModel: SessionTableViewModel, NavigationItemSource, Navigatabl case clearData } - // MARK: - Navigation + // MARK: - NavigationItemSource - lazy var navState: AnyPublisher = { - Publishers - .CombineLatest( - isEditing, - textChanged - .handleEvents( - receiveOutput: { [weak self] value, _ in - self?.editedDisplayName = value - } - ) - .filter { _ in false } - .prepend((nil, .profileName)) - ) - .map { isEditing, _ -> NavState in (isEditing ? .editing : .standard) } - .removeDuplicates() - .prepend(.standard) // Initial value - .shareReplay(1) - .eraseToAnyPublisher() - }() + lazy var navState: AnyPublisher = Publishers + .CombineLatest( + isEditing, + textChanged + .handleEvents( + receiveOutput: { [weak self] value, _ in + self?.editedDisplayName = value + } + ) + .filter { _ in false } + .prepend((nil, .profileName)) + ) + .map { isEditing, _ -> NavState in (isEditing ? .editing : .standard) } + .removeDuplicates() + .prepend(.standard) // Initial value + .shareReplay(1) + .eraseToAnyPublisher() lazy var leftNavItems: AnyPublisher<[SessionNavItem], Never> = navState .map { navState -> [SessionNavItem] in @@ -164,7 +162,7 @@ class SettingsViewModel: SessionTableViewModel, NavigationItemSource, Navigatabl } ) ] - + case .editing: return [ SessionNavItem( @@ -210,9 +208,10 @@ class SettingsViewModel: SessionTableViewModel, NavigationItemSource, Navigatabl ) } ] - } } - .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + // MARK: - Content diff --git a/Session/Shared/Types/ObservableTableSource.swift b/Session/Shared/Types/ObservableTableSource.swift index b2d4f2c22..1671a6678 100644 --- a/Session/Shared/Types/ObservableTableSource.swift +++ b/Session/Shared/Types/ObservableTableSource.swift @@ -156,7 +156,7 @@ public enum ObservationBuilder { .trackingConstantRegion(fetch) .removeDuplicates() .handleEvents(didFail: { SNLog("[\(type(of: viewModel))] Observation failed with error: \($0)") }) - .publisher(in: dependencies.storage, scheduling: dependencies.scheduler) + .publisher(in: dependencies[singleton: .storage], scheduling: dependencies[singleton: .scheduler]) .manualRefreshFrom(source.observableState.forcedRefresh) } } @@ -173,7 +173,7 @@ public enum ObservationBuilder { .trackingConstantRegion(fetch) .removeDuplicates() .handleEvents(didFail: { SNLog("[\(type(of: viewModel))] Observation failed with error: \($0)") }) - .publisher(in: dependencies.storage, scheduling: dependencies.scheduler) + .publisher(in: dependencies[singleton: .storage], scheduling: dependencies[singleton: .scheduler]) .manualRefreshFrom(source.observableState.forcedRefresh) } } diff --git a/Session/Shared/Types/PagedObservationSource.swift b/Session/Shared/Types/PagedObservationSource.swift index a7bf8c795..33c0da78e 100644 --- a/Session/Shared/Types/PagedObservationSource.swift +++ b/Session/Shared/Types/PagedObservationSource.swift @@ -17,7 +17,7 @@ protocol PagedObservationSource { extension PagedObservationSource { public func didInit(using dependencies: Dependencies) { - dependencies.storage.addObserver(pagedDataObserver) + dependencies[singleton: .storage].addObserver(pagedDataObserver) } } diff --git a/SessionMessagingKit/Database/Models/ClosedGroup.swift b/SessionMessagingKit/Database/Models/ClosedGroup.swift index 4ffb154bf..5b380c13a 100644 --- a/SessionMessagingKit/Database/Models/ClosedGroup.swift +++ b/SessionMessagingKit/Database/Models/ClosedGroup.swift @@ -189,7 +189,7 @@ public extension ClosedGroup { throw MessageReceiverError.noUserED25519KeyPair } - if group.invited == false { + if group.invited == true { try ClosedGroup .filter(id: group.id) .updateAllAndConfig( diff --git a/SessionMessagingKit/Jobs/Types/ExpirationUpdateJob.swift b/SessionMessagingKit/Jobs/Types/ExpirationUpdateJob.swift index 0ba170643..df4e1aba4 100644 --- a/SessionMessagingKit/Jobs/Types/ExpirationUpdateJob.swift +++ b/SessionMessagingKit/Jobs/Types/ExpirationUpdateJob.swift @@ -30,25 +30,23 @@ public enum ExpirationUpdateJob: JobExecutor { dependencies[singleton: .storage] .readPublisher(using: dependencies) { db in - try SnodeAPI.AuthenticationInfo( - db, - sessionIdHexString: getUserSessionId(db, using: dependencies).hexString, - using: dependencies - ) - } - .flatMap { authInfo in - SnodeAPI - .updateExpiry( + try SnodeAPI + .preparedUpdateExpiry( serverHashes: details.serverHashes, updatedExpiryMs: details.expirationTimestampMs, shortenOnly: true, - authInfo: authInfo, + authInfo: try SnodeAPI.AuthenticationInfo( + db, + sessionIdHexString: getUserSessionId(db, using: dependencies).hexString, + using: dependencies + ), using: dependencies ) } + .flatMap { $0.send(using: dependencies) } .subscribe(on: queue, using: dependencies) .receive(on: queue, using: dependencies) - .map { response -> [UInt64: [String]] in + .map { _, response -> [UInt64: [String]] in guard let results: [UpdateExpiryResponseResult] = response .compactMap({ _, value in value.didError ? nil : value }) diff --git a/SessionMessagingKit/Jobs/Types/GetExpirationJob.swift b/SessionMessagingKit/Jobs/Types/GetExpirationJob.swift index 6947af0ae..6f7e62c63 100644 --- a/SessionMessagingKit/Jobs/Types/GetExpirationJob.swift +++ b/SessionMessagingKit/Jobs/Types/GetExpirationJob.swift @@ -43,31 +43,23 @@ public enum GetExpirationJob: JobExecutor { } let userSessionId: SessionId = getUserSessionId(using: dependencies) - SnodeAPI - .getSwarm(for: userSessionId.hexString, using: dependencies) - .tryFlatMap { swarm -> AnyPublisher<(ResponseInfoType, GetExpiriesResponse), Error> in - guard let snode = swarm.randomElement() else { throw SnodeAPIError.generic } - return dependencies[singleton: .storage] - .readPublisher(using: dependencies) { db in - try SnodeAPI.AuthenticationInfo( + + return dependencies[singleton: .storage] + .readPublisher(using: dependencies) { db in + try SnodeAPI + .preparedGetExpiries( + of: expirationInfo.map { $0.key }, + authInfo: try SnodeAPI.AuthenticationInfo( db, sessionIdHexString: userSessionId.hexString, using: dependencies - ) - } - .flatMap { authInfo in - SnodeAPI.getExpiries( - from: snode, - of: expirationInfo.map { $0.key }, - authInfo: authInfo, - using: dependencies - ) - } - .eraseToAnyPublisher() + ), + using: dependencies + ) } + .flatMap { $0.send(using: dependencies) } .subscribe(on: queue, using: dependencies) .receive(on: queue, using: dependencies) - .map { _, response -> GetExpiriesResponse in response } .sinkUntilComplete( receiveCompletion: { result in switch result { @@ -75,7 +67,7 @@ public enum GetExpirationJob: JobExecutor { case .failure(let error): failure(job, error, true, dependencies) } }, - receiveValue: { response in + receiveValue: { _, response in let serverSpecifiedExpirationStartTimesMs: [String: TimeInterval] = response.expiries .reduce(into: [:]) { result, next in guard let expiresInSeconds: TimeInterval = expirationInfo[next.key] else { return } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift index 6dcb9db7f..dd653bee0 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+UnsendRequests.swift @@ -48,16 +48,19 @@ extension MessageReceiver { if author == message.sender, let serverHash: String = interaction.serverHash { dependencies[singleton: .storage] .readPublisher(using: dependencies) { db in - try SnodeAPI.AuthenticationInfo(db, sessionIdHexString: author, using: dependencies) - } - .flatMap { authInfo in - SnodeAPI - .deleteMessages( + try SnodeAPI + .preparedDeleteMessages( serverHashes: [serverHash], - authInfo: authInfo, + requireSuccessfulDeletion: false, + authInfo: try SnodeAPI.AuthenticationInfo( + db, + sessionIdHexString: author, + using: dependencies + ), using: dependencies ) } + .flatMap { $0.send(using: dependencies) } .subscribe(on: DispatchQueue.global(qos: .background), using: dependencies) .sinkUntilComplete() } diff --git a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift index 65918301d..3eaf1a8fc 100644 --- a/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift +++ b/SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+Groups.swift @@ -167,7 +167,7 @@ extension MessageSender { groupSessionId: String, name: String, displayPicture: SignalAttachment?, - members: [(String, Profile?)], + members: [(id: String, profile: Profile?, isAdmin: Bool)], using dependencies: Dependencies = Dependencies() ) -> AnyPublisher { guard (try? SessionId.Prefix(from: groupSessionId)) == .group else { @@ -194,6 +194,25 @@ extension MessageSender { .filter(id: groupSessionId) .updateAllAndConfig(db, ClosedGroup.Columns.name.set(to: name), using: dependencies) } + + // Retrieve member info + guard let allGroupMembers: [GroupMember] = try? closedGroup.allMembers.fetchAll(db) else { + throw MessageSenderError.invalidClosedGroupUpdate + } + + let originalMemberIds: Set = allGroupMembers.map { $0.profileId }.asSet() + let addedMembers: [(id: String, profile: Profile?, isAdmin: Bool)] = members + .filter { !originalMemberIds.contains($0.0) } + let removedMemberIds: Set = originalMemberIds + .subtracting(members.map { id, _, _ in id }.asSet()) + + // Update libSession (libSession will figure out if it's member list changed) + try? SessionUtil.update( + db, + groupSessionId: groupSessionId, + members: members, + using: dependencies + ) } .eraseToAnyPublisher() } diff --git a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+GroupMembers.swift b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+GroupMembers.swift index f571fda25..3b34ef819 100644 --- a/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+GroupMembers.swift +++ b/SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+GroupMembers.swift @@ -113,6 +113,51 @@ internal extension SessionUtil { // MARK: - Outgoing Changes internal extension SessionUtil { + static func update( + _ db: Database, + groupSessionId: String, + groupIdentityPrivateKey: Data? = nil, + members: [(id: String, profile: Profile?, isAdmin: Bool)], + using dependencies: Dependencies + ) throws { + // Reduce the members list to ensure we don't accidentally insert duplicates (which can crash) + let finalMembers: [String: (profile: Profile?, isAdmin: Bool)] = members + .reduce(into: [:]) { result, next in result[next.0] = (profile: next.1, isAdmin: next.2)} + + try SessionUtil.performAndPushChange( + db, + for: .groupMembers, + sessionId: SessionId(.group, hex: groupSessionId), + using: dependencies + ) { config in + guard case .object(let conf) = config else { throw SessionUtilError.invalidConfigObject } + + try finalMembers.forEach { memberId, info in + var profilePic: user_profile_pic = user_profile_pic() + + if + let picUrl: String = info.profile?.profilePictureUrl, + let picKey: Data = info.profile?.profileEncryptionKey + { + profilePic.url = picUrl.toLibSession() + profilePic.key = picKey.toLibSession() + } + + try CExceptionHelper.performSafely { + var member: config_group_member = config_group_member( + session_id: memberId.toLibSession(), + name: (info.profile?.name ?? "").toLibSession(), + profile_pic: profilePic, + admin: info.isAdmin, + invited: 0, + promoted: 0 + ) + + groups_members_set(conf, &member) + } + } + } + } } // MARK: - MemberData diff --git a/SessionSnodeKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift b/SessionSnodeKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift index b6dd7435d..b8a12a3f0 100644 --- a/SessionSnodeKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift +++ b/SessionSnodeKit/Database/Migrations/_005_AddSnodeReveivedMessageInfoPrimaryKey.swift @@ -16,7 +16,7 @@ enum _005_AddSnodeReveivedMessageInfoPrimaryKey: Migration { /// messages from the beginning of time) static let minExpectedRunDuration: TimeInterval = 0.2 - static func migrate(_ db: Database) throws { + static func migrate(_ db: Database, using dependencies: Dependencies) throws { // SQLite doesn't support adding a new primary key after creation so we need to create a new table with // the setup we want, copy data from the old table over, drop the old table and rename the new table struct TmpSnodeReceivedMessageInfo: Codable, TableRecord, FetchableRecord, PersistableRecord, ColumnExpressible { @@ -67,6 +67,6 @@ enum _005_AddSnodeReveivedMessageInfoPrimaryKey: Migration { try db.createIndex(on: SnodeReceivedMessageInfo.self, columns: [.expirationDateMs]) try db.createIndex(on: SnodeReceivedMessageInfo.self, columns: [.wasDeletedOrInvalid]) - Storage.update(progress: 1, for: self, in: target) + Storage.update(progress: 1, for: self, in: target, using: dependencies) } } diff --git a/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift b/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift index 26feeec3f..5e44b1ee1 100644 --- a/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift +++ b/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift @@ -73,14 +73,6 @@ public extension SnodeReceivedMessageInfo { associatedWith publicKey: String, using dependencies: Dependencies ) throws { - // Only prune the hashes if new hashes exist for this Snode (if they don't then - // we don't want to clear out the legacy hashes) - let hasNonLegacyHash: Bool = SnodeReceivedMessageInfo - .filter(SnodeReceivedMessageInfo.Columns.key == key(for: snode, publicKey: publicKey, namespace: namespace)) - .isNotEmpty(db) - - guard hasNonLegacyHash else { return } - let rowIds: [Int64] = try SnodeReceivedMessageInfo .select(Column.rowID) .filter(SnodeReceivedMessageInfo.Columns.key == key(for: snode, publicKey: publicKey, namespace: namespace)) @@ -97,10 +89,6 @@ public extension SnodeReceivedMessageInfo { } /// This method fetches the last non-expired hash from the database for message retrieval - /// - /// **Note:** This method uses a `write` instead of a read because there is a single write queue for the database and it's - /// very common for this method to be called after the hash value has been updated but before the various `read` threads - /// have been updated, resulting in a pointless fetch for data the app has already received static func fetchLastNotExpired( _ db: Database, for snode: Snode, @@ -108,7 +96,7 @@ public extension SnodeReceivedMessageInfo { associatedWith publicKey: String, using dependencies: Dependencies ) throws -> SnodeReceivedMessageInfo? { - let nonLegacyHash: SnodeReceivedMessageInfo? = try SnodeReceivedMessageInfo + return try SnodeReceivedMessageInfo .filter( SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == nil || SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == false @@ -117,19 +105,6 @@ public extension SnodeReceivedMessageInfo { .filter(SnodeReceivedMessageInfo.Columns.expirationDateMs > SnodeAPI.currentOffsetTimestampMs()) .order(Column.rowID.desc) .fetchOne(db) - - // If we have a non-legacy hash then return it immediately (legacy hashes had a different - // 'key' structure) - if nonLegacyHash != nil { return nonLegacyHash } - - return try SnodeReceivedMessageInfo - .filter( - SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == nil || - SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == false - ) - .filter(SnodeReceivedMessageInfo.Columns.key == publicKey) - .order(Column.rowID.desc) - .fetchOne(db) } /// There are some cases where the latest message can be removed from a swarm, if we then try to poll for that message the swarm diff --git a/SessionSnodeKit/Models/GetMessagesResponse.swift b/SessionSnodeKit/Models/GetMessagesResponse.swift index e5e8fe74b..ee5905f82 100644 --- a/SessionSnodeKit/Models/GetMessagesResponse.swift +++ b/SessionSnodeKit/Models/GetMessagesResponse.swift @@ -10,13 +10,13 @@ public class GetMessagesResponse: SnodeResponse { public class RawMessage: Codable { private enum CodingKeys: String, CodingKey { - case data + case base64EncodedDataString = "data" case expiration case hash case timestampMs = "timestamp" } - public let data: String + public let base64EncodedDataString: String public let expiration: Int64? public let hash: String public let timestampMs: Int64 diff --git a/SessionSnodeKit/Models/SnodeReceivedMessage.swift b/SessionSnodeKit/Models/SnodeReceivedMessage.swift index 404565065..0c275c61f 100644 --- a/SessionSnodeKit/Models/SnodeReceivedMessage.swift +++ b/SessionSnodeKit/Models/SnodeReceivedMessage.swift @@ -19,7 +19,7 @@ public struct SnodeReceivedMessage: CustomDebugStringConvertible { namespace: SnodeAPI.Namespace, rawMessage: GetMessagesResponse.RawMessage ) { - guard let data: Data = Data(base64Encoded: rawMessage.data) else { + guard let data: Data = Data(base64Encoded: rawMessage.base64EncodedDataString) else { SNLog("Failed to decode data for message: \(rawMessage).") return nil } diff --git a/SessionSnodeKit/Networking/SnodeAPI.swift b/SessionSnodeKit/Networking/SnodeAPI.swift index 048f11649..00f686bad 100644 --- a/SessionSnodeKit/Networking/SnodeAPI.swift +++ b/SessionSnodeKit/Networking/SnodeAPI.swift @@ -549,99 +549,31 @@ public final class SnodeAPI { .eraseToAnyPublisher() } - public static func getExpiries( - from snode: Snode, + public static func preparedGetExpiries( of serverHashes: [String], authInfo: AuthenticationInfo, using dependencies: Dependencies = Dependencies() - ) -> AnyPublisher<(ResponseInfoType, GetExpiriesResponse), Error> { - let sendTimestamp: UInt64 = UInt64(SnodeAPI.currentOffsetTimestampMs(using: dependencies)) - + ) throws -> HTTP.PreparedRequest { // FIXME: There is a bug on SS now that a single-hash lookup is not working. Remove it when the bug is fixed let serverHashes: [String] = serverHashes.appending("fakehash") - return SnodeAPI - .send( - request: SnodeRequest( + return try SnodeAPI + .prepareRequest( + request: Request( endpoint: .getExpiries, + publicKey: authInfo.sessionId.hexString, body: GetExpiriesRequest( messageHashes: serverHashes, authInfo: authInfo, - timestampMs: sendTimestamp + timestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs(using: dependencies)) ) ), - to: snode, - associatedWith: authInfo.sessionId.hexString, - using: dependencies + responseType: GetExpiriesResponse.self ) - .decoded(as: GetExpiriesResponse.self, using: dependencies) - .eraseToAnyPublisher() } // MARK: - Store - public static func sendMessage( - _ message: SnodeMessage, - in namespace: Namespace, - authInfo: AuthenticationInfo, - using dependencies: Dependencies - ) -> AnyPublisher<(ResponseInfoType, SendMessagesResponse), Error> { - let sendTimestamp: UInt64 = UInt64(SnodeAPI.currentOffsetTimestampMs(using: dependencies)) - - // Create a convenience method to send a message to an individual Snode - func sendMessage(to snode: Snode) throws -> AnyPublisher<(any ResponseInfoType, SendMessagesResponse), Error> { - guard namespace.requiresWriteAuthentication else { - return SnodeAPI - .send( - request: SnodeRequest( - endpoint: .sendMessage, - body: LegacySendMessagesRequest( - message: message, - namespace: namespace - ) - ), - to: snode, - associatedWith: authInfo.sessionId.hexString, - using: dependencies - ) - .decoded(as: SendMessagesResponse.self, using: dependencies) - .eraseToAnyPublisher() - } - - return SnodeAPI - .send( - request: SnodeRequest( - endpoint: .sendMessage, - body: SendMessageRequest( - message: message, - namespace: namespace, - authInfo: authInfo, - timestampMs: sendTimestamp - ) - ), - to: snode, - associatedWith: authInfo.sessionId.hexString, - using: dependencies - ) - .decoded(as: SendMessagesResponse.self, using: dependencies) - .eraseToAnyPublisher() - } - - return getSwarm(for: authInfo.sessionId.hexString) - .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<(ResponseInfoType, SendMessagesResponse), Error> in - try sendMessage(to: snode) - .tryMap { info, response -> (ResponseInfoType, SendMessagesResponse) in - try response.validateResultMap( - publicKey: authInfo.sessionId.hexString, - using: dependencies - ) - - return (info, response) - } - .eraseToAnyPublisher() - } - } - public static func preparedSendMessage( _ db: Database, message: SnodeMessage, @@ -693,131 +625,70 @@ public final class SnodeAPI { // MARK: - Edit - public static func updateExpiry( + public static func preparedUpdateExpiry( serverHashes: [String], updatedExpiryMs: Int64, shortenOnly: Bool? = nil, extendOnly: Bool? = nil, authInfo: AuthenticationInfo, using dependencies: Dependencies = Dependencies() - ) -> AnyPublisher<[String: UpdateExpiryResponseResult], Error> { + ) throws -> HTTP.PreparedRequest<[String: UpdateExpiryResponseResult]> { // ShortenOnly and extendOnly cannot be true at the same time - guard shortenOnly == nil || extendOnly == nil else { - return Fail(error: SnodeAPIError.generic) - .eraseToAnyPublisher() - } + guard shortenOnly == nil || extendOnly == nil else { throw SnodeAPIError.generic } - return getSwarm(for: authInfo.sessionId.hexString) - .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: UpdateExpiryResponseResult], Error> in - SnodeAPI - .send( - request: SnodeRequest( - endpoint: .expire, - body: UpdateExpiryRequest( - messageHashes: serverHashes, - expiryMs: UInt64(updatedExpiryMs), - shorten: shortenOnly, - extend: extendOnly, - authInfo: authInfo - ) - ), - to: snode, - associatedWith: authInfo.sessionId.hexString, - using: dependencies + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .expire, + publicKey: authInfo.sessionId.hexString, + body: UpdateExpiryRequest( + messageHashes: serverHashes, + expiryMs: UInt64(updatedExpiryMs), + shorten: shortenOnly, + extend: extendOnly, + authInfo: authInfo ) - .decoded(as: UpdateExpiryResponse.self, using: dependencies) - .tryMap { _, response -> [String: UpdateExpiryResponseResult] in - try response.validResultMap( - publicKey: authInfo.sessionId.hexString, - validationData: serverHashes, - using: dependencies - ) - } - .eraseToAnyPublisher() + ), + responseType: UpdateExpiryResponse.self + ) + .tryMap { _, response -> [String: UpdateExpiryResponseResult] in + try response.validResultMap( + publicKey: authInfo.sessionId.hexString, + validationData: serverHashes, + using: dependencies + ) } } - public static func revokeSubkey( + public static func preparedRevokeSubkey( subkeyToRevoke: String, authInfo: AuthenticationInfo, using dependencies: Dependencies = Dependencies() - ) -> AnyPublisher { - return getSwarm(for: authInfo.sessionId.hexString) - .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher in - SnodeAPI - .send( - request: SnodeRequest( - endpoint: .revokeSubkey, - body: RevokeSubkeyRequest( - subkeyToRevoke: subkeyToRevoke, - authInfo: authInfo - ) - ), - to: snode, - associatedWith: authInfo.sessionId.hexString, - using: dependencies + ) throws -> HTTP.PreparedRequest { + return try SnodeAPI + .prepareRequest( + request: Request( + endpoint: .revokeSubkey, + publicKey: authInfo.sessionId.hexString, + body: RevokeSubkeyRequest( + subkeyToRevoke: subkeyToRevoke, + authInfo: authInfo ) - .decoded(as: RevokeSubkeyResponse.self, using: dependencies) - .tryMap { _, response -> Void in - try response.validateResultMap( - publicKey: authInfo.sessionId.hexString, - validationData: subkeyToRevoke, - using: dependencies - ) - - return () - } - .eraseToAnyPublisher() + ), + responseType: RevokeSubkeyResponse.self + ) + .tryMap { _, response -> Void in + try response.validateResultMap( + publicKey: authInfo.sessionId.hexString, + validationData: subkeyToRevoke, + using: dependencies + ) + + return () } } - // MARK: Delete - - public static func deleteMessages( - serverHashes: [String], - authInfo: AuthenticationInfo, - using dependencies: Dependencies = Dependencies() - ) -> AnyPublisher<[String: Bool], Error> { - return getSwarm(for: authInfo.sessionId.hexString, using: dependencies) - .tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: Bool], Error> in - SnodeAPI - .send( - request: SnodeRequest( - endpoint: .deleteMessages, - body: DeleteMessagesRequest( - messageHashes: serverHashes, - requireSuccessfulDeletion: false, - authInfo: authInfo - ) - ), - to: snode, - associatedWith: authInfo.sessionId.hexString, - using: dependencies - ) - .decoded(as: DeleteMessagesResponse.self, using: dependencies) - .tryMap { _, response -> [String: Bool] in - let validResultMap: [String: Bool] = try response.validResultMap( - publicKey: authInfo.sessionId.hexString, - validationData: serverHashes, - using: dependencies - ) - - // If `validResultMap` didn't throw then at least one service node - // deleted successfully so we should mark the hash as invalid so we - // don't try to fetch updates using that hash going forward (if we - // do we would end up re-fetching all old messages) - dependencies[singleton: .storage].writeAsync { db in - try? SnodeReceivedMessageInfo.handlePotentialDeletedOrInvalidHash( - db, - potentiallyInvalidHashes: serverHashes - ) - } - - return validResultMap - } - .eraseToAnyPublisher() - } - } + // MARK: - Delete public static func preparedDeleteMessages( serverHashes: [String], diff --git a/SessionSnodeKit/Types/SnodeAPINamespace.swift b/SessionSnodeKit/Types/SnodeAPINamespace.swift index e1d6cf5ed..0508a0073 100644 --- a/SessionSnodeKit/Types/SnodeAPINamespace.swift +++ b/SessionSnodeKit/Types/SnodeAPINamespace.swift @@ -6,7 +6,7 @@ import Foundation import SessionUtilitiesKit public extension SnodeAPI { - enum Namespace: Int, Codable, Hashable { + enum Namespace: Int, Codable, Hashable, CustomStringConvertible { /// Messages sent to one-to-one conversations are stored in this namespace case `default` = 0 @@ -25,14 +25,14 @@ public extension SnodeAPI { /// Messages sent to an updated closed group are stored in this namespace case groupMessages = 11 + /// `GROUP_KEYS` config messages (encryption/decryption keys for messages within a specific group) + case configGroupKeys = 12 + /// `GROUP_INFO` config messages (general info about a specific group) - case configGroupInfo = 12 + case configGroupInfo = 13 /// `GROUP_MEMBERS` config messages (member information for a specific group) - case configGroupMembers = 13 - - /// `GROUP_KEYS` config messages (encryption/decryption keys for messages within a specific group) - case configGroupKeys = 14 + case configGroupMembers = 14 /// Messages sent to legacy group conversations are stored in this namespace case legacyClosedGroup = -10 @@ -148,5 +148,25 @@ public extension SnodeAPI { result[next.namespace] = -next.maxSize } } + + // MARK: - CustomStringConvertible + + public var description: String { + switch self { + case .`default`: return "default" + case .configUserProfile: return "configUserProfile" + case .configContacts: return "configContacts" + case .configConvoInfoVolatile: return "configConvoInfoVolatile" + case .configUserGroups: return "configUserGroups" + case .groupMessages: return "groupMessages" + case .configGroupInfo: return "configGroupInfo" + case .configGroupMembers: return "configGroupMembers" + case .configGroupKeys: return "configGroupKeys" + case .legacyClosedGroup: return "legacyClosedGroup" + + case .unknown: return "unknown" + case .all: return "all" + } + } } } diff --git a/SessionTests/Settings/NotificationContentViewModelSpec.swift b/SessionTests/Settings/NotificationContentViewModelSpec.swift index 02fd0b51b..4e2d45baf 100644 --- a/SessionTests/Settings/NotificationContentViewModelSpec.swift +++ b/SessionTests/Settings/NotificationContentViewModelSpec.swift @@ -30,7 +30,7 @@ class NotificationContentViewModelSpec: QuickSpec { @TestState var viewModel: NotificationContentViewModel! = NotificationContentViewModel( using: dependencies ) - @TestState var dataChangeCancellable: AnyCancellable? = viewModel.observableTableData + @TestState var dataChangeCancellable: AnyCancellable? = viewModel.tableDataPublisher .receive(on: ImmediateScheduler.shared) .sink( receiveCompletion: { _ in }, diff --git a/SessionUtilitiesKit/Database/Models/Identity.swift b/SessionUtilitiesKit/Database/Models/Identity.swift index 5afaa7930..fc7b62c89 100644 --- a/SessionUtilitiesKit/Database/Models/Identity.swift +++ b/SessionUtilitiesKit/Database/Models/Identity.swift @@ -55,7 +55,7 @@ public extension Identity { .toX25519(ed25519PublicKey: ed25519KeyPair.publicKey) ), let x25519SecretKey: [UInt8] = try? dependencies[singleton: .crypto].perform( - .toX25519(ed25519PublicKey: ed25519KeyPair.secretKey) + .toX25519(ed25519SecretKey: ed25519KeyPair.secretKey) ) else { throw GeneralError.keyGenerationFailed } diff --git a/SessionUtilitiesKit/Networking/PreparedRequest.swift b/SessionUtilitiesKit/Networking/PreparedRequest.swift index b04603c5c..26b635640 100644 --- a/SessionUtilitiesKit/Networking/PreparedRequest.swift +++ b/SessionUtilitiesKit/Networking/PreparedRequest.swift @@ -726,4 +726,3 @@ public extension Publisher where Output == (ResponseInfoType, Data?), Failure == private protocol _OptionalProtocol {} extension Optional: _OptionalProtocol {} -