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
pull/941/head
Morgan Pretty 2 years ago
parent 5ac05a41ec
commit 8e04944af0

@ -79,7 +79,7 @@ extension ProjectState {
.regex("case .* = "), .regex("case .* = "),
.regex("Error.*\\("), .regex("Error.*\\("),
.regex("Crypto.*\\(id:"), .regex("Crypto.*\\(id:"),
.containsAnd("id:", .previousLine(numEarlier: 1, .regex("Crypto.*\\("))) .containsAnd("id:", caseSensitive: false, .previousLine(numEarlier: 1, .regex("Crypto.*\\(")))
] ]
} }

@ -529,7 +529,6 @@
FD1D732E2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */; }; FD1D732E2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */; };
FD1F9C9F2A862BE60050F671 /* MigrationRequirement.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1F9C9E2A862BE60050F671 /* MigrationRequirement.swift */; }; FD1F9C9F2A862BE60050F671 /* MigrationRequirement.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1F9C9E2A862BE60050F671 /* MigrationRequirement.swift */; };
FD23CE1B2A651E6D0000B97C /* NetworkType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1A2A651E6D0000B97C /* NetworkType.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 */; }; 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 */; }; FD23CE242A675C440000B97C /* Crypto+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE232A675C440000B97C /* Crypto+SessionMessagingKit.swift */; };
FD23CE262A676B5B0000B97C /* DependenciesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE252A676B5B0000B97C /* DependenciesSpec.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 */; }; FD30036E2A3AE26000B5A5FB /* CExceptionHelper.mm in Sources */ = {isa = PBXBuildFile; fileRef = FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */; };
FD368A6829DE8F9C000DBF1E /* _012_AddFTSIfNeeded.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */; }; 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 */; }; 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 */; }; FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */; };
FD37E9C628A1D4EC003AE748 /* Theme+ClassicDark.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.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 */; }; 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 = "<group>"; }; FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _015_BlockCommunityMessageRequests.swift; sourceTree = "<group>"; };
FD1F9C9E2A862BE60050F671 /* MigrationRequirement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationRequirement.swift; sourceTree = "<group>"; }; FD1F9C9E2A862BE60050F671 /* MigrationRequirement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationRequirement.swift; sourceTree = "<group>"; };
FD23CE1A2A651E6D0000B97C /* NetworkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkType.swift; sourceTree = "<group>"; }; FD23CE1A2A651E6D0000B97C /* NetworkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkType.swift; sourceTree = "<group>"; };
FD23CE1E2A65269C0000B97C /* Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Crypto.swift; sourceTree = "<group>"; };
FD23CE212A661D000000B97C /* Crypto+OpenGroupAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+OpenGroupAPI.swift"; sourceTree = "<group>"; }; FD23CE212A661D000000B97C /* Crypto+OpenGroupAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+OpenGroupAPI.swift"; sourceTree = "<group>"; };
FD23CE232A675C440000B97C /* Crypto+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+SessionMessagingKit.swift"; sourceTree = "<group>"; }; FD23CE232A675C440000B97C /* Crypto+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+SessionMessagingKit.swift"; sourceTree = "<group>"; };
FD23CE252A676B5B0000B97C /* DependenciesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependenciesSpec.swift; sourceTree = "<group>"; }; FD23CE252A676B5B0000B97C /* DependenciesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependenciesSpec.swift; sourceTree = "<group>"; };
@ -1787,6 +1786,7 @@
FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CExceptionHelper.mm; sourceTree = "<group>"; }; FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CExceptionHelper.mm; sourceTree = "<group>"; };
FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _012_AddFTSIfNeeded.swift; sourceTree = "<group>"; }; FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _012_AddFTSIfNeeded.swift; sourceTree = "<group>"; };
FD368A6929DE9E30000DBF1E /* UIContextualAction+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Utilities.swift"; sourceTree = "<group>"; }; FD368A6929DE9E30000DBF1E /* UIContextualAction+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIContextualAction+Utilities.swift"; sourceTree = "<group>"; };
FD3765D92AD7C91D00DC1489 /* Crypto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Crypto.swift; sourceTree = "<group>"; };
FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; }; FD37E9C228A1C6F3003AE748 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; };
FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+ClassicDark.swift"; sourceTree = "<group>"; }; FD37E9C528A1D4EC003AE748 /* Theme+ClassicDark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+ClassicDark.swift"; sourceTree = "<group>"; };
FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Colors.swift"; sourceTree = "<group>"; }; FD37E9C728A1D73F003AE748 /* Theme+Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Colors.swift"; sourceTree = "<group>"; };
@ -2729,7 +2729,7 @@
B8A582AC258C653C00AFD84C /* Crypto */ = { B8A582AC258C653C00AFD84C /* Crypto */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FD23CE1E2A65269C0000B97C /* Crypto.swift */, FD3765D92AD7C91D00DC1489 /* Crypto.swift */,
FD9AECA62AAAF5B0009B3406 /* Crypto+SessionUtilitiesKit.swift */, FD9AECA62AAAF5B0009B3406 /* Crypto+SessionUtilitiesKit.swift */,
FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */, FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */,
B88FA7FA26114EA70049422F /* Hex.swift */, B88FA7FA26114EA70049422F /* Hex.swift */,
@ -3745,7 +3745,6 @@
FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */, FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */,
FD3003692A3ADD6000B5A5FB /* CExceptionHelper.h */, FD3003692A3ADD6000B5A5FB /* CExceptionHelper.h */,
FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */, FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */,
FD23CE1E2A65269C0000B97C /* Crypto.swift */,
FD12A84A2AD6458800EEBA0D /* DifferenceKit+Utilities.swift */, FD12A84A2AD6458800EEBA0D /* DifferenceKit+Utilities.swift */,
FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */, FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */,
FD09796A27F6C67500936362 /* Failable.swift */, FD09796A27F6C67500936362 /* Failable.swift */,
@ -6136,6 +6135,7 @@
FDFBB74B2A1EFF4900CA7350 /* Bencode.swift in Sources */, FDFBB74B2A1EFF4900CA7350 /* Bencode.swift in Sources */,
FDE519F72AB7CDC700450C53 /* Result+Utilities.swift in Sources */, FDE519F72AB7CDC700450C53 /* Result+Utilities.swift in Sources */,
FD5931A72A8DA5DA0040147D /* SQLInterpolation+Utilities.swift in Sources */, FD5931A72A8DA5DA0040147D /* SQLInterpolation+Utilities.swift in Sources */,
FD3765DA2AD7C91E00DC1489 /* Crypto.swift in Sources */,
FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */, FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */,
FDF8487929405906007DCAE5 /* HTTPQueryParam.swift in Sources */, FDF8487929405906007DCAE5 /* HTTPQueryParam.swift in Sources */,
FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */, FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */,
@ -6148,7 +6148,6 @@
FD9AECA72AAAF5B0009B3406 /* Crypto+SessionUtilitiesKit.swift in Sources */, FD9AECA72AAAF5B0009B3406 /* Crypto+SessionUtilitiesKit.swift in Sources */,
C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */, C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */,
FD12A84B2AD6458800EEBA0D /* DifferenceKit+Utilities.swift in Sources */, FD12A84B2AD6458800EEBA0D /* DifferenceKit+Utilities.swift in Sources */,
C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */,
FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */, FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */,
FD5931AB2A8DCB0A0040147D /* ScopeAdapter+Utilities.swift in Sources */, FD5931AB2A8DCB0A0040147D /* ScopeAdapter+Utilities.swift in Sources */,
FD37E9FF28A5F2CD003AE748 /* Configuration.swift in Sources */, FD37E9FF28A5F2CD003AE748 /* Configuration.swift in Sources */,
@ -6181,7 +6180,6 @@
C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */, C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */,
B8F5F58325EC94A6003BF8D4 /* Collection+Utilities.swift in Sources */, B8F5F58325EC94A6003BF8D4 /* Collection+Utilities.swift in Sources */,
7BD477A827EC39F5004E2822 /* Atomic.swift in Sources */, 7BD477A827EC39F5004E2822 /* Atomic.swift in Sources */,
FD23CE1F2A65269C0000B97C /* Crypto.swift in Sources */,
B8BC00C0257D90E30032E807 /* General.swift in Sources */, B8BC00C0257D90E30032E807 /* General.swift in Sources */,
FDF8488629405A61007DCAE5 /* Request.swift in Sources */, FDF8488629405A61007DCAE5 /* Request.swift in Sources */,
FD23CE302A67B8820000B97C /* CacheConfig.swift in Sources */, FD23CE302A67B8820000B97C /* CacheConfig.swift in Sources */,

@ -361,6 +361,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat
let title: String = "vc_conversation_settings_invite_button_title".localized() let title: String = "vc_conversation_settings_invite_button_title".localized()
let userSessionId: SessionId = self.userSessionId let userSessionId: SessionId = self.userSessionId
let threadVariant: SessionThread.Variant = self.threadVariant
let userSelectionVC: UserSelectionVC = UserSelectionVC( let userSelectionVC: UserSelectionVC = UserSelectionVC(
with: title, with: title,
excluding: allGroupMembers excluding: allGroupMembers
@ -405,7 +406,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat
return (lhsDisplayName < rhsDisplayName) return (lhsDisplayName < rhsDisplayName)
}) })
.filter { $0.role == .standard || $0.role == .zombie } .filter { $0.role != .zombie }
let uniqueGroupMemberIds: Set<String> = (self?.allGroupMembers ?? []) let uniqueGroupMemberIds: Set<String> = (self?.allGroupMembers ?? [])
.map { $0.profileId } .map { $0.profileId }
@ -442,8 +443,8 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat
let threadId: String = self.threadId let threadId: String = self.threadId
let updatedName: String = self.name let updatedName: String = self.name
let userSessionId: SessionId = self.userSessionId let userSessionId: SessionId = self.userSessionId
let updatedMembers: [(String, Profile?)] = self.allGroupMembers let updatedMembers: [(id: String, profile: Profile?, isAdmin: Bool)] = self.allGroupMembers
.map { ($0.profileId, $0.profile) } .map { ($0.profileId, $0.profile, ($0.role == .admin)) }
let updatedMemberIds: Set<String> = updatedMembers.map { $0.0 }.asSet() let updatedMemberIds: Set<String> = updatedMembers.map { $0.0 }.asSet()
guard updatedMemberIds != self.originalMembersIds || updatedName != self.originalName else { guard updatedMemberIds != self.originalMembersIds || updatedName != self.originalName else {

@ -1839,15 +1839,14 @@ extension ConversationVC:
func delete(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) { func delete(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
switch cellViewModel.variant { switch cellViewModel.variant {
case .standardIncomingDeleted, .infoCall, case .standardIncomingDeleted, .infoCall, .infoScreenshotNotification, .infoMediaSavedNotification,
.infoScreenshotNotification, .infoMediaSavedNotification, .infoClosedGroupCreated, .infoClosedGroupUpdated, .infoClosedGroupCurrentUserLeft,
.infoClosedGroupCreated, .infoClosedGroupUpdated, .infoClosedGroupCurrentUserLeaving, .infoClosedGroupCurrentUserErrorLeaving,
.infoClosedGroupCurrentUserLeft, .infoClosedGroupCurrentUserLeaving, .infoClosedGroupCurrentUserErrorLeaving,
.infoMessageRequestAccepted, .infoDisappearingMessagesUpdate: .infoMessageRequestAccepted, .infoDisappearingMessagesUpdate:
// Info messages and unsent messages should just trigger a local // Info messages and unsent messages should just trigger a local
// deletion (they are created as side effects so we wouldn't be // deletion (they are created as side effects so we wouldn't be
// able to delete them for all participants anyway) // able to delete them for all participants anyway)
Dependencies()[singleton: .storage].writeAsync { db in dependencies[singleton: .storage].writeAsync { db in
_ = try Interaction _ = try Interaction
.filter(id: cellViewModel.id) .filter(id: cellViewModel.id)
.deleteAll(db) .deleteAll(db)
@ -1858,8 +1857,8 @@ extension ConversationVC:
} }
let threadName: String = self.viewModel.threadData.displayName let threadName: String = self.viewModel.threadData.displayName
let userSessionId: SessionId = getUserSessionId(using: dependencies) let userSessionId: SessionId = getUserSessionId(using: dependencies)
// Remote deletion logic // Remote deletion logic
func deleteRemotely(from viewController: UIViewController?, request: AnyPublisher<Void, Error>, onComplete: (() -> ())?) { func deleteRemotely(from viewController: UIViewController?, request: AnyPublisher<Void, Error>, onComplete: (() -> ())?) {
// Show a loading indicator // Show a loading indicator
@ -2105,19 +2104,19 @@ extension ConversationVC:
from: self, from: self,
request: dependencies[singleton: .storage] request: dependencies[singleton: .storage]
.readPublisher(using: dependencies) { db in .readPublisher(using: dependencies) { db in
try SnodeAPI.AuthenticationInfo( try SnodeAPI
db, .preparedDeleteMessages(
sessionIdHexString: targetPublicKey,
using: dependencies
)
}
.flatMap { authInfo in
SnodeAPI
.deleteMessages(
serverHashes: [serverHash], serverHashes: [serverHash],
authInfo: authInfo requireSuccessfulDeletion: false,
authInfo: try SnodeAPI.AuthenticationInfo(
db,
sessionIdHexString: targetPublicKey,
using: dependencies
),
using: dependencies
) )
} }
.flatMap { $0.send(using: dependencies) }
.map { _ in () } .map { _ in () }
.eraseToAnyPublisher() .eraseToAnyPublisher()
) { completeServerDeletion() } ) { completeServerDeletion() }

@ -513,7 +513,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel, Naviga
} }
// Contacts & legacy closed groups need to update the SessionUtil // 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 { switch threadVariant {
case .contact: case .contact:
try SessionUtil try SessionUtil

@ -37,13 +37,6 @@ public class HomeViewModel {
init( init(
using dependencies: Dependencies = Dependencies() using dependencies: Dependencies = Dependencies()
) { ) {
typealias InitialData = (
userSessionId: SessionId,
showViewedSeedBanner: Bool,
hasHiddenMessageRequests: Bool,
profile: Profile
)
let initialState: State? = dependencies[singleton: .storage].read { db -> State in let initialState: State? = dependencies[singleton: .storage].read { db -> State in
try HomeViewModel.retrieveState(db, excludingMessageRequestThreadCount: true, using: dependencies) try HomeViewModel.retrieveState(db, excludingMessageRequestThreadCount: true, using: dependencies)
} }

@ -33,6 +33,7 @@ class MessageRequestsViewModel: SessionTableViewModel, NavigatableStateHolder, O
// distinct stutter) // distinct stutter)
let userSessionId: SessionId = getUserSessionId(using: dependencies) let userSessionId: SessionId = getUserSessionId(using: dependencies)
let thread: TypedTableAlias<SessionThread> = TypedTableAlias() let thread: TypedTableAlias<SessionThread> = TypedTableAlias()
self.pagedDataObserver = PagedDatabaseObserver( self.pagedDataObserver = PagedDatabaseObserver(
pagedTable: SessionThread.self, pagedTable: SessionThread.self,
pageSize: MessageRequestsViewModel.pageSize, pageSize: MessageRequestsViewModel.pageSize,
@ -225,7 +226,8 @@ class MessageRequestsViewModel: SessionTableViewModel, NavigatableStateHolder, O
.map { id, _ in id }, .map { id, _ in id },
threadVariant: .contact, threadVariant: .contact,
groupLeaveType: .silent, groupLeaveType: .silent,
calledFromConfigHandling: false calledFromConfigHandling: false,
using: dependencies
) )
// Remove the group requests // Remove the group requests
@ -236,7 +238,8 @@ class MessageRequestsViewModel: SessionTableViewModel, NavigatableStateHolder, O
.map { id, _ in id }, .map { id, _ in id },
threadVariant: .group, threadVariant: .group,
groupLeaveType: .silent, groupLeaveType: .silent,
calledFromConfigHandling: false calledFromConfigHandling: false,
using: dependencies
) )
} }
} }

@ -170,7 +170,7 @@ public class BlockedContactsViewModel: SessionTableViewModel, NavigatableStateHo
let contactNames: [String] = contactIds let contactNames: [String] = contactIds
.compactMap { contactId in .compactMap { contactId in
guard guard
let section: BlockedContactsViewModel.SectionModel = self.tableData let section: SectionModel = self.tableData
.first(where: { section in section.model == .contacts }), .first(where: { section in section.model == .contacts }),
let info: SessionCell.Info<TableItem> = section.elements let info: SessionCell.Info<TableItem> = section.elements
.first(where: { info in info.id.id == contactId }) .first(where: { info in info.id.id == contactId })

@ -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 // MARK: - Content
private struct State: Equatable { private struct State: Equatable {

@ -50,18 +50,6 @@ class NotificationSettingsViewModel: SessionTableViewModel, NavigatableStateHold
case content case content
} }
private let dependencies: Dependencies
// MARK: - Initialization
init(
using dependencies: Dependencies = Dependencies()
) {
self.dependencies = dependencies
super.init()
}
// MARK: - Content // MARK: - Content
private struct State: Equatable { private struct State: Equatable {
@ -84,7 +72,7 @@ class NotificationSettingsViewModel: SessionTableViewModel, NavigatableStateHold
.defaulting(to: Preferences.NotificationPreviewType.defaultPreviewType) .defaulting(to: Preferences.NotificationPreviewType.defaultPreviewType)
) )
} }
.map { dbState -> State in .map { [dependencies] dbState -> State in
State( State(
isUsingFullAPNs: dependencies[defaults: .standard, key: .isUsingFullAPNs], isUsingFullAPNs: dependencies[defaults: .standard, key: .isUsingFullAPNs],
notificationSound: dbState.notificationSound, notificationSound: dbState.notificationSound,

@ -18,7 +18,6 @@ class NotificationSoundViewModel: SessionTableViewModel, NavigationItemSource, N
// FIXME: Remove `threadId` once we ditch the per-thread notification sound // FIXME: Remove `threadId` once we ditch the per-thread notification sound
private let threadId: String? private let threadId: String?
private let dependencies: Dependencies
private var audioPlayer: OWSAudioPlayer? private var audioPlayer: OWSAudioPlayer?
private var storedSelection: Preferences.Sound? private var storedSelection: Preferences.Sound?
private var currentSelection: CurrentValueSubject<Preferences.Sound?, Never> = CurrentValueSubject(nil) private var currentSelection: CurrentValueSubject<Preferences.Sound?, Never> = CurrentValueSubject(nil)
@ -28,7 +27,6 @@ class NotificationSoundViewModel: SessionTableViewModel, NavigationItemSource, N
init(threadId: String? = nil, using dependencies: Dependencies = Dependencies()) { init(threadId: String? = nil, using dependencies: Dependencies = Dependencies()) {
self.dependencies = dependencies self.dependencies = dependencies
self.threadId = threadId self.threadId = threadId
self.dependencies = dependencies
} }
deinit { deinit {

@ -97,27 +97,25 @@ class SettingsViewModel: SessionTableViewModel, NavigationItemSource, Navigatabl
case clearData case clearData
} }
// MARK: - Navigation // MARK: - NavigationItemSource
lazy var navState: AnyPublisher<NavState, Never> = { lazy var navState: AnyPublisher<NavState, Never> = Publishers
Publishers .CombineLatest(
.CombineLatest( isEditing,
isEditing, textChanged
textChanged .handleEvents(
.handleEvents( receiveOutput: { [weak self] value, _ in
receiveOutput: { [weak self] value, _ in self?.editedDisplayName = value
self?.editedDisplayName = value }
} )
) .filter { _ in false }
.filter { _ in false } .prepend((nil, .profileName))
.prepend((nil, .profileName)) )
) .map { isEditing, _ -> NavState in (isEditing ? .editing : .standard) }
.map { isEditing, _ -> NavState in (isEditing ? .editing : .standard) } .removeDuplicates()
.removeDuplicates() .prepend(.standard) // Initial value
.prepend(.standard) // Initial value .shareReplay(1)
.shareReplay(1) .eraseToAnyPublisher()
.eraseToAnyPublisher()
}()
lazy var leftNavItems: AnyPublisher<[SessionNavItem<NavItem>], Never> = navState lazy var leftNavItems: AnyPublisher<[SessionNavItem<NavItem>], Never> = navState
.map { navState -> [SessionNavItem<NavItem>] in .map { navState -> [SessionNavItem<NavItem>] in
@ -210,9 +208,10 @@ class SettingsViewModel: SessionTableViewModel, NavigationItemSource, Navigatabl
) )
} }
] ]
}
} }
.eraseToAnyPublisher() }
.eraseToAnyPublisher()
// MARK: - Content // MARK: - Content

@ -156,7 +156,7 @@ public enum ObservationBuilder {
.trackingConstantRegion(fetch) .trackingConstantRegion(fetch)
.removeDuplicates() .removeDuplicates()
.handleEvents(didFail: { SNLog("[\(type(of: viewModel))] Observation failed with error: \($0)") }) .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) .manualRefreshFrom(source.observableState.forcedRefresh)
} }
} }
@ -173,7 +173,7 @@ public enum ObservationBuilder {
.trackingConstantRegion(fetch) .trackingConstantRegion(fetch)
.removeDuplicates() .removeDuplicates()
.handleEvents(didFail: { SNLog("[\(type(of: viewModel))] Observation failed with error: \($0)") }) .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) .manualRefreshFrom(source.observableState.forcedRefresh)
} }
} }

@ -17,7 +17,7 @@ protocol PagedObservationSource {
extension PagedObservationSource { extension PagedObservationSource {
public func didInit(using dependencies: Dependencies) { public func didInit(using dependencies: Dependencies) {
dependencies.storage.addObserver(pagedDataObserver) dependencies[singleton: .storage].addObserver(pagedDataObserver)
} }
} }

@ -189,7 +189,7 @@ public extension ClosedGroup {
throw MessageReceiverError.noUserED25519KeyPair throw MessageReceiverError.noUserED25519KeyPair
} }
if group.invited == false { if group.invited == true {
try ClosedGroup try ClosedGroup
.filter(id: group.id) .filter(id: group.id)
.updateAllAndConfig( .updateAllAndConfig(

@ -30,25 +30,23 @@ public enum ExpirationUpdateJob: JobExecutor {
dependencies[singleton: .storage] dependencies[singleton: .storage]
.readPublisher(using: dependencies) { db in .readPublisher(using: dependencies) { db in
try SnodeAPI.AuthenticationInfo( try SnodeAPI
db, .preparedUpdateExpiry(
sessionIdHexString: getUserSessionId(db, using: dependencies).hexString,
using: dependencies
)
}
.flatMap { authInfo in
SnodeAPI
.updateExpiry(
serverHashes: details.serverHashes, serverHashes: details.serverHashes,
updatedExpiryMs: details.expirationTimestampMs, updatedExpiryMs: details.expirationTimestampMs,
shortenOnly: true, shortenOnly: true,
authInfo: authInfo, authInfo: try SnodeAPI.AuthenticationInfo(
db,
sessionIdHexString: getUserSessionId(db, using: dependencies).hexString,
using: dependencies
),
using: dependencies using: dependencies
) )
} }
.flatMap { $0.send(using: dependencies) }
.subscribe(on: queue, using: dependencies) .subscribe(on: queue, using: dependencies)
.receive(on: queue, using: dependencies) .receive(on: queue, using: dependencies)
.map { response -> [UInt64: [String]] in .map { _, response -> [UInt64: [String]] in
guard guard
let results: [UpdateExpiryResponseResult] = response let results: [UpdateExpiryResponseResult] = response
.compactMap({ _, value in value.didError ? nil : value }) .compactMap({ _, value in value.didError ? nil : value })

@ -43,31 +43,23 @@ public enum GetExpirationJob: JobExecutor {
} }
let userSessionId: SessionId = getUserSessionId(using: dependencies) let userSessionId: SessionId = getUserSessionId(using: dependencies)
SnodeAPI
.getSwarm(for: userSessionId.hexString, using: dependencies) return dependencies[singleton: .storage]
.tryFlatMap { swarm -> AnyPublisher<(ResponseInfoType, GetExpiriesResponse), Error> in .readPublisher(using: dependencies) { db in
guard let snode = swarm.randomElement() else { throw SnodeAPIError.generic } try SnodeAPI
return dependencies[singleton: .storage] .preparedGetExpiries(
.readPublisher(using: dependencies) { db in of: expirationInfo.map { $0.key },
try SnodeAPI.AuthenticationInfo( authInfo: try SnodeAPI.AuthenticationInfo(
db, db,
sessionIdHexString: userSessionId.hexString, sessionIdHexString: userSessionId.hexString,
using: dependencies using: dependencies
) ),
} using: dependencies
.flatMap { authInfo in )
SnodeAPI.getExpiries(
from: snode,
of: expirationInfo.map { $0.key },
authInfo: authInfo,
using: dependencies
)
}
.eraseToAnyPublisher()
} }
.flatMap { $0.send(using: dependencies) }
.subscribe(on: queue, using: dependencies) .subscribe(on: queue, using: dependencies)
.receive(on: queue, using: dependencies) .receive(on: queue, using: dependencies)
.map { _, response -> GetExpiriesResponse in response }
.sinkUntilComplete( .sinkUntilComplete(
receiveCompletion: { result in receiveCompletion: { result in
switch result { switch result {
@ -75,7 +67,7 @@ public enum GetExpirationJob: JobExecutor {
case .failure(let error): failure(job, error, true, dependencies) case .failure(let error): failure(job, error, true, dependencies)
} }
}, },
receiveValue: { response in receiveValue: { _, response in
let serverSpecifiedExpirationStartTimesMs: [String: TimeInterval] = response.expiries let serverSpecifiedExpirationStartTimesMs: [String: TimeInterval] = response.expiries
.reduce(into: [:]) { result, next in .reduce(into: [:]) { result, next in
guard let expiresInSeconds: TimeInterval = expirationInfo[next.key] else { return } guard let expiresInSeconds: TimeInterval = expirationInfo[next.key] else { return }

@ -48,16 +48,19 @@ extension MessageReceiver {
if author == message.sender, let serverHash: String = interaction.serverHash { if author == message.sender, let serverHash: String = interaction.serverHash {
dependencies[singleton: .storage] dependencies[singleton: .storage]
.readPublisher(using: dependencies) { db in .readPublisher(using: dependencies) { db in
try SnodeAPI.AuthenticationInfo(db, sessionIdHexString: author, using: dependencies) try SnodeAPI
} .preparedDeleteMessages(
.flatMap { authInfo in
SnodeAPI
.deleteMessages(
serverHashes: [serverHash], serverHashes: [serverHash],
authInfo: authInfo, requireSuccessfulDeletion: false,
authInfo: try SnodeAPI.AuthenticationInfo(
db,
sessionIdHexString: author,
using: dependencies
),
using: dependencies using: dependencies
) )
} }
.flatMap { $0.send(using: dependencies) }
.subscribe(on: DispatchQueue.global(qos: .background), using: dependencies) .subscribe(on: DispatchQueue.global(qos: .background), using: dependencies)
.sinkUntilComplete() .sinkUntilComplete()
} }

@ -167,7 +167,7 @@ extension MessageSender {
groupSessionId: String, groupSessionId: String,
name: String, name: String,
displayPicture: SignalAttachment?, displayPicture: SignalAttachment?,
members: [(String, Profile?)], members: [(id: String, profile: Profile?, isAdmin: Bool)],
using dependencies: Dependencies = Dependencies() using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<Void, Error> { ) -> AnyPublisher<Void, Error> {
guard (try? SessionId.Prefix(from: groupSessionId)) == .group else { guard (try? SessionId.Prefix(from: groupSessionId)) == .group else {
@ -194,6 +194,25 @@ extension MessageSender {
.filter(id: groupSessionId) .filter(id: groupSessionId)
.updateAllAndConfig(db, ClosedGroup.Columns.name.set(to: name), using: dependencies) .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<String> = allGroupMembers.map { $0.profileId }.asSet()
let addedMembers: [(id: String, profile: Profile?, isAdmin: Bool)] = members
.filter { !originalMemberIds.contains($0.0) }
let removedMemberIds: Set<String> = 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() .eraseToAnyPublisher()
} }

@ -113,6 +113,51 @@ internal extension SessionUtil {
// MARK: - Outgoing Changes // MARK: - Outgoing Changes
internal extension SessionUtil { 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 // MARK: - MemberData

@ -16,7 +16,7 @@ enum _005_AddSnodeReveivedMessageInfoPrimaryKey: Migration {
/// messages from the beginning of time) /// messages from the beginning of time)
static let minExpectedRunDuration: TimeInterval = 0.2 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 // 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 // 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 { 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: [.expirationDateMs])
try db.createIndex(on: SnodeReceivedMessageInfo.self, columns: [.wasDeletedOrInvalid]) 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)
} }
} }

@ -73,14 +73,6 @@ public extension SnodeReceivedMessageInfo {
associatedWith publicKey: String, associatedWith publicKey: String,
using dependencies: Dependencies using dependencies: Dependencies
) throws { ) 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 let rowIds: [Int64] = try SnodeReceivedMessageInfo
.select(Column.rowID) .select(Column.rowID)
.filter(SnodeReceivedMessageInfo.Columns.key == key(for: snode, publicKey: publicKey, namespace: namespace)) .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 /// 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( static func fetchLastNotExpired(
_ db: Database, _ db: Database,
for snode: Snode, for snode: Snode,
@ -108,7 +96,7 @@ public extension SnodeReceivedMessageInfo {
associatedWith publicKey: String, associatedWith publicKey: String,
using dependencies: Dependencies using dependencies: Dependencies
) throws -> SnodeReceivedMessageInfo? { ) throws -> SnodeReceivedMessageInfo? {
let nonLegacyHash: SnodeReceivedMessageInfo? = try SnodeReceivedMessageInfo return try SnodeReceivedMessageInfo
.filter( .filter(
SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == nil || SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == nil ||
SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == false SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == false
@ -117,19 +105,6 @@ public extension SnodeReceivedMessageInfo {
.filter(SnodeReceivedMessageInfo.Columns.expirationDateMs > SnodeAPI.currentOffsetTimestampMs()) .filter(SnodeReceivedMessageInfo.Columns.expirationDateMs > SnodeAPI.currentOffsetTimestampMs())
.order(Column.rowID.desc) .order(Column.rowID.desc)
.fetchOne(db) .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 /// 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

@ -10,13 +10,13 @@ public class GetMessagesResponse: SnodeResponse {
public class RawMessage: Codable { public class RawMessage: Codable {
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case data case base64EncodedDataString = "data"
case expiration case expiration
case hash case hash
case timestampMs = "timestamp" case timestampMs = "timestamp"
} }
public let data: String public let base64EncodedDataString: String
public let expiration: Int64? public let expiration: Int64?
public let hash: String public let hash: String
public let timestampMs: Int64 public let timestampMs: Int64

@ -19,7 +19,7 @@ public struct SnodeReceivedMessage: CustomDebugStringConvertible {
namespace: SnodeAPI.Namespace, namespace: SnodeAPI.Namespace,
rawMessage: GetMessagesResponse.RawMessage 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).") SNLog("Failed to decode data for message: \(rawMessage).")
return nil return nil
} }

@ -549,99 +549,31 @@ public final class SnodeAPI {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
public static func getExpiries( public static func preparedGetExpiries(
from snode: Snode,
of serverHashes: [String], of serverHashes: [String],
authInfo: AuthenticationInfo, authInfo: AuthenticationInfo,
using dependencies: Dependencies = Dependencies() using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<(ResponseInfoType, GetExpiriesResponse), Error> { ) throws -> HTTP.PreparedRequest<GetExpiriesResponse> {
let sendTimestamp: UInt64 = UInt64(SnodeAPI.currentOffsetTimestampMs(using: dependencies))
// FIXME: There is a bug on SS now that a single-hash lookup is not working. Remove it when the bug is fixed // 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") let serverHashes: [String] = serverHashes.appending("fakehash")
return SnodeAPI return try SnodeAPI
.send( .prepareRequest(
request: SnodeRequest( request: Request(
endpoint: .getExpiries, endpoint: .getExpiries,
publicKey: authInfo.sessionId.hexString,
body: GetExpiriesRequest( body: GetExpiriesRequest(
messageHashes: serverHashes, messageHashes: serverHashes,
authInfo: authInfo, authInfo: authInfo,
timestampMs: sendTimestamp timestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs(using: dependencies))
) )
), ),
to: snode, responseType: GetExpiriesResponse.self
associatedWith: authInfo.sessionId.hexString,
using: dependencies
) )
.decoded(as: GetExpiriesResponse.self, using: dependencies)
.eraseToAnyPublisher()
} }
// MARK: - Store // 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( public static func preparedSendMessage(
_ db: Database, _ db: Database,
message: SnodeMessage, message: SnodeMessage,
@ -693,131 +625,70 @@ public final class SnodeAPI {
// MARK: - Edit // MARK: - Edit
public static func updateExpiry( public static func preparedUpdateExpiry(
serverHashes: [String], serverHashes: [String],
updatedExpiryMs: Int64, updatedExpiryMs: Int64,
shortenOnly: Bool? = nil, shortenOnly: Bool? = nil,
extendOnly: Bool? = nil, extendOnly: Bool? = nil,
authInfo: AuthenticationInfo, authInfo: AuthenticationInfo,
using dependencies: Dependencies = Dependencies() using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<[String: UpdateExpiryResponseResult], Error> { ) throws -> HTTP.PreparedRequest<[String: UpdateExpiryResponseResult]> {
// ShortenOnly and extendOnly cannot be true at the same time // ShortenOnly and extendOnly cannot be true at the same time
guard shortenOnly == nil || extendOnly == nil else { guard shortenOnly == nil || extendOnly == nil else { throw SnodeAPIError.generic }
return Fail(error: SnodeAPIError.generic)
.eraseToAnyPublisher()
}
return getSwarm(for: authInfo.sessionId.hexString) return try SnodeAPI
.tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: UpdateExpiryResponseResult], Error> in .prepareRequest(
SnodeAPI request: Request(
.send( endpoint: .expire,
request: SnodeRequest( publicKey: authInfo.sessionId.hexString,
endpoint: .expire, body: UpdateExpiryRequest(
body: UpdateExpiryRequest( messageHashes: serverHashes,
messageHashes: serverHashes, expiryMs: UInt64(updatedExpiryMs),
expiryMs: UInt64(updatedExpiryMs), shorten: shortenOnly,
shorten: shortenOnly, extend: extendOnly,
extend: extendOnly, authInfo: authInfo
authInfo: authInfo
)
),
to: snode,
associatedWith: authInfo.sessionId.hexString,
using: dependencies
) )
.decoded(as: UpdateExpiryResponse.self, using: dependencies) ),
.tryMap { _, response -> [String: UpdateExpiryResponseResult] in responseType: UpdateExpiryResponse.self
try response.validResultMap( )
publicKey: authInfo.sessionId.hexString, .tryMap { _, response -> [String: UpdateExpiryResponseResult] in
validationData: serverHashes, try response.validResultMap(
using: dependencies publicKey: authInfo.sessionId.hexString,
) validationData: serverHashes,
} using: dependencies
.eraseToAnyPublisher() )
} }
} }
public static func revokeSubkey( public static func preparedRevokeSubkey(
subkeyToRevoke: String, subkeyToRevoke: String,
authInfo: AuthenticationInfo, authInfo: AuthenticationInfo,
using dependencies: Dependencies = Dependencies() using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<Void, Error> { ) throws -> HTTP.PreparedRequest<Void> {
return getSwarm(for: authInfo.sessionId.hexString) return try SnodeAPI
.tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<Void, Error> in .prepareRequest(
SnodeAPI request: Request(
.send( endpoint: .revokeSubkey,
request: SnodeRequest( publicKey: authInfo.sessionId.hexString,
endpoint: .revokeSubkey, body: RevokeSubkeyRequest(
body: RevokeSubkeyRequest( subkeyToRevoke: subkeyToRevoke,
subkeyToRevoke: subkeyToRevoke, authInfo: authInfo
authInfo: authInfo
)
),
to: snode,
associatedWith: authInfo.sessionId.hexString,
using: dependencies
) )
.decoded(as: RevokeSubkeyResponse.self, using: dependencies) ),
.tryMap { _, response -> Void in responseType: RevokeSubkeyResponse.self
try response.validateResultMap( )
publicKey: authInfo.sessionId.hexString, .tryMap { _, response -> Void in
validationData: subkeyToRevoke, try response.validateResultMap(
using: dependencies publicKey: authInfo.sessionId.hexString,
) validationData: subkeyToRevoke,
using: dependencies
)
return () return ()
}
.eraseToAnyPublisher()
} }
} }
// MARK: Delete // 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()
}
}
public static func preparedDeleteMessages( public static func preparedDeleteMessages(
serverHashes: [String], serverHashes: [String],

@ -6,7 +6,7 @@ import Foundation
import SessionUtilitiesKit import SessionUtilitiesKit
public extension SnodeAPI { 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 /// Messages sent to one-to-one conversations are stored in this namespace
case `default` = 0 case `default` = 0
@ -25,14 +25,14 @@ public extension SnodeAPI {
/// Messages sent to an updated closed group are stored in this namespace /// Messages sent to an updated closed group are stored in this namespace
case groupMessages = 11 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) /// `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) /// `GROUP_MEMBERS` config messages (member information for a specific group)
case configGroupMembers = 13 case configGroupMembers = 14
/// `GROUP_KEYS` config messages (encryption/decryption keys for messages within a specific group)
case configGroupKeys = 14
/// Messages sent to legacy group conversations are stored in this namespace /// Messages sent to legacy group conversations are stored in this namespace
case legacyClosedGroup = -10 case legacyClosedGroup = -10
@ -148,5 +148,25 @@ public extension SnodeAPI {
result[next.namespace] = -next.maxSize 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"
}
}
} }
} }

@ -30,7 +30,7 @@ class NotificationContentViewModelSpec: QuickSpec {
@TestState var viewModel: NotificationContentViewModel! = NotificationContentViewModel( @TestState var viewModel: NotificationContentViewModel! = NotificationContentViewModel(
using: dependencies using: dependencies
) )
@TestState var dataChangeCancellable: AnyCancellable? = viewModel.observableTableData @TestState var dataChangeCancellable: AnyCancellable? = viewModel.tableDataPublisher
.receive(on: ImmediateScheduler.shared) .receive(on: ImmediateScheduler.shared)
.sink( .sink(
receiveCompletion: { _ in }, receiveCompletion: { _ in },

@ -55,7 +55,7 @@ public extension Identity {
.toX25519(ed25519PublicKey: ed25519KeyPair.publicKey) .toX25519(ed25519PublicKey: ed25519KeyPair.publicKey)
), ),
let x25519SecretKey: [UInt8] = try? dependencies[singleton: .crypto].perform( let x25519SecretKey: [UInt8] = try? dependencies[singleton: .crypto].perform(
.toX25519(ed25519PublicKey: ed25519KeyPair.secretKey) .toX25519(ed25519SecretKey: ed25519KeyPair.secretKey)
) )
else { throw GeneralError.keyGenerationFailed } else { throw GeneralError.keyGenerationFailed }

@ -726,4 +726,3 @@ public extension Publisher where Output == (ResponseInfoType, Data?), Failure ==
private protocol _OptionalProtocol {} private protocol _OptionalProtocol {}
extension Optional: _OptionalProtocol {} extension Optional: _OptionalProtocol {}

Loading…
Cancel
Save