From 712d873e866cc15deab50abde01fa12b772ad4a7 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Tue, 17 Dec 2024 15:09:42 -0800 Subject: [PATCH] Sync All Conversations Consent Filtering (#449) * add consent sync filtering * update async methods * update async methods * more fixes * add missing async modifier * bump podspec to 3.0.18 --------- Co-authored-by: cameronvoell --- Package.resolved | 4 +- Package.swift | 2 +- Sources/XMTPiOS/Client.swift | 18 +- Sources/XMTPiOS/Conversation.swift | 4 +- Sources/XMTPiOS/Conversations.swift | 154 ++++++++++-------- Sources/XMTPiOS/Dm.swift | 14 +- Sources/XMTPiOS/Extensions/Ffi.swift | 4 +- Sources/XMTPiOS/Group.swift | 14 +- Tests/XMTPTests/ConversationTests.swift | 36 +++- Tests/XMTPTests/GroupPermissionsTests.swift | 6 +- Tests/XMTPTests/GroupTests.swift | 8 +- XMTP.podspec | 4 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- 13 files changed, 159 insertions(+), 113 deletions(-) diff --git a/Package.resolved b/Package.resolved index c0ca7c96..ed272fb7 100644 --- a/Package.resolved +++ b/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/xmtp/libxmtp-swift.git", "state" : { - "revision" : "d6670069b6f7ae52415c691dbca4c37fbdaf87b4", - "version" : "3.0.12" + "revision" : "203fd6d67bb72e3114b273ce9bbddd6fc747d583", + "version" : "3.0.13" } }, { diff --git a/Package.swift b/Package.swift index 624f0cc1..46fe7a30 100644 --- a/Package.swift +++ b/Package.swift @@ -21,7 +21,7 @@ let package = Package( .package(url: "https://github.com/bufbuild/connect-swift", exact: "1.0.0"), .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.4.3"), .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", exact: "1.8.3"), - .package(url: "https://github.com/xmtp/libxmtp-swift.git", exact: "3.0.12") + .package(url: "https://github.com/xmtp/libxmtp-swift.git", exact: "3.0.13") ], targets: [ .target( diff --git a/Sources/XMTPiOS/Client.swift b/Sources/XMTPiOS/Client.swift index 3c2e546b..96155a0b 100644 --- a/Sources/XMTPiOS/Client.swift +++ b/Sources/XMTPiOS/Client.swift @@ -204,8 +204,9 @@ public final class Client { let dbURL = directoryURL.appendingPathComponent(alias).path let ffiClient = try await LibXMTP.createClient( - host: options.api.env.url, - isSecure: options.api.env.isSecure == true, + api: connectToBackend( + host: options.api.env.url, + isSecure: options.api.env.isSecure == true), db: dbURL, encryptionKey: options.dbEncryptionKey, inboxId: inboxId, @@ -297,8 +298,9 @@ public final class Client { let dbURL = directoryURL.appendingPathComponent(alias).path let ffiClient = try await LibXMTP.createClient( - host: api.env.url, - isSecure: api.env.isSecure == true, + api: connectToBackend( + host: api.env.url, + isSecure: api.env.isSecure == true), db: dbURL, encryptionKey: nil, inboxId: inboxId, @@ -448,18 +450,18 @@ public final class Client { } } - public func findConversation(conversationId: String) throws -> Conversation? + public func findConversation(conversationId: String) async throws -> Conversation? { do { let conversation = try ffiClient.conversation( conversationId: conversationId.hexToData) - return try conversation.toConversation(client: self) + return try await conversation.toConversation(client: self) } catch { return nil } } - public func findConversationByTopic(topic: String) throws -> Conversation? { + public func findConversationByTopic(topic: String) async throws -> Conversation? { do { let regexPattern = #"/xmtp/mls/1/g-(.*?)/proto"# if let regex = try? NSRegularExpression(pattern: regexPattern) { @@ -471,7 +473,7 @@ public final class Client { with: match.range(at: 1)) let conversation = try ffiClient.conversation( conversationId: conversationId.hexToData) - return try conversation.toConversation(client: self) + return try await conversation.toConversation(client: self) } } } catch { diff --git a/Sources/XMTPiOS/Conversation.swift b/Sources/XMTPiOS/Conversation.swift index cddcc4c3..b84075d8 100644 --- a/Sources/XMTPiOS/Conversation.swift +++ b/Sources/XMTPiOS/Conversation.swift @@ -29,9 +29,9 @@ public enum Conversation: Identifiable, Equatable, Hashable { public func isCreator() async throws -> Bool { switch self { case let .group(group): - return try group.isCreator() + return try await group.isCreator() case let .dm(dm): - return try dm.isCreator() + return try await dm.isCreator() } } diff --git a/Sources/XMTPiOS/Conversations.swift b/Sources/XMTPiOS/Conversations.swift index ef7a73d1..ec3d4e43 100644 --- a/Sources/XMTPiOS/Conversations.swift +++ b/Sources/XMTPiOS/Conversations.swift @@ -79,9 +79,11 @@ public actor Conversations { public func sync() async throws { try await ffiConversations.sync() } - public func syncAllConversations() async throws -> UInt32 { - // TODO: add consent state here - return try await ffiConversations.syncAllConversations(consentState: nil) + public func syncAllConversations(consentState: ConsentState? = nil) + async throws -> UInt32 + { + return try await ffiConversations.syncAllConversations( + consentState: consentState?.toFFI) } public func listGroups( @@ -105,7 +107,7 @@ public actor Conversations { let conversations = try await ffiConversations.listGroups( opts: options) - let sortedConversations = try sortConversations( + let sortedConversations = try await sortConversations( conversations, order: order) return sortedConversations.map { @@ -134,7 +136,7 @@ public actor Conversations { let conversations = try await ffiConversations.listDms( opts: options) - let sortedConversations = try sortConversations( + let sortedConversations = try await sortConversations( conversations, order: order) return sortedConversations.map { @@ -160,36 +162,42 @@ public actor Conversations { if let limit { options.limit = Int64(limit) } - let conversations = try await ffiConversations.list( + let ffiConversations = try await ffiConversations.list( opts: options) - let sortedConversations = try sortConversations( - conversations, order: order) + let sortedConversations = try await sortConversations( + ffiConversations, order: order) + + var conversations: [Conversation] = [] + for sortedConversation in sortedConversations { + let conversation = try await sortedConversation.toConversation(client: client) + conversations.append(conversation) + } - return try sortedConversations.map { - try $0.toConversation(client: client) - } + return conversations } private func sortConversations( _ conversations: [FfiConversation], order: ConversationOrder - ) throws -> [FfiConversation] { + ) async throws -> [FfiConversation] { switch order { case .lastMessage: - let conversationWithTimestamp: [(FfiConversation, Int64?)] = - try conversations.map { conversation in - let message = try conversation.findMessages( - opts: FfiListMessagesOptions( - sentBeforeNs: nil, - sentAfterNs: nil, - limit: 1, - deliveryStatus: nil, - direction: .descending - ) - ).first - return (conversation, message?.sentAtNs) - } + var conversationWithTimestamp: [(FfiConversation, Int64?)] = [] + + for conversation in conversations { + let message = try await conversation.findMessages( + opts: FfiListMessagesOptions( + sentBeforeNs: nil, + sentAfterNs: nil, + limit: 1, + deliveryStatus: nil, + direction: .descending + ) + ).first + conversationWithTimestamp.append( + (conversation, message?.sentAtNs)) + } let sortedTuples = conversationWithTimestamp.sorted { (lhs, rhs) in (lhs.1 ?? 0) > (rhs.1 ?? 0) @@ -207,41 +215,45 @@ public actor Conversations { let ffiStreamActor = FfiStreamActor() let conversationCallback = ConversationStreamCallback { conversation in - guard !Task.isCancelled else { - continuation.finish() - return - } - do { - let conversationType = try conversation.conversationType() - if conversationType == .dm { - continuation.yield( - Conversation.dm( - conversation.dmFromFFI(client: self.client)) - ) - } else if conversationType == .group { - continuation.yield( - Conversation.group( - conversation.groupFromFFI(client: self.client)) - ) + Task { + guard !Task.isCancelled else { + continuation.finish() + return + } + do { + let conversationType = + try await conversation.conversationType() + if conversationType == .dm { + continuation.yield( + Conversation.dm( + conversation.dmFromFFI(client: self.client)) + ) + } else if conversationType == .group { + continuation.yield( + Conversation.group( + conversation.groupFromFFI( + client: self.client)) + ) + } + } catch { + print("Error processing conversation type: \(error)") } - } catch { - // Do nothing if the conversation type is neither a group or dm } } let task = Task { let stream: FfiStreamCloser - switch type { - case .groups: - stream = await ffiConversations.streamGroups( - callback: conversationCallback) - case .all: - stream = await ffiConversations.stream( - callback: conversationCallback) - case .dms: - stream = await ffiConversations.streamDms( - callback: conversationCallback) - } + switch type { + case .groups: + stream = await ffiConversations.streamGroups( + callback: conversationCallback) + case .all: + stream = await ffiConversations.stream( + callback: conversationCallback) + case .dms: + stream = await ffiConversations.streamDms( + callback: conversationCallback) + } await ffiStreamActor.setFfiStream(stream) continuation.onTermination = { @Sendable reason in Task { @@ -268,7 +280,9 @@ public actor Conversations { if !canMessage { throw ConversationError.memberNotRegistered([peerAddress]) } - if let existingDm = try await client.findDmByAddress(address: peerAddress) { + if let existingDm = try await client.findDmByAddress( + address: peerAddress) + { return existingDm } @@ -385,20 +399,20 @@ public actor Conversations { let task = Task { let stream: FfiStreamCloser - switch type { - case .groups: - stream = await ffiConversations.streamAllGroupMessages( - messageCallback: messageCallback - ) - case .dms: - stream = await ffiConversations.streamAllDmMessages( - messageCallback: messageCallback - ) - case .all: - stream = await ffiConversations.streamAllMessages( - messageCallback: messageCallback - ) - } + switch type { + case .groups: + stream = await ffiConversations.streamAllGroupMessages( + messageCallback: messageCallback + ) + case .dms: + stream = await ffiConversations.streamAllDmMessages( + messageCallback: messageCallback + ) + case .all: + stream = await ffiConversations.streamAllMessages( + messageCallback: messageCallback + ) + } await ffiStreamActor.setFfiStream(stream) } @@ -417,7 +431,7 @@ public actor Conversations { let conversation = try await ffiConversations .processStreamedWelcomeMessage(envelopeBytes: envelopeBytes) - return try conversation.toConversation(client: client) + return try await conversation.toConversation(client: client) } public func newConversation( diff --git a/Sources/XMTPiOS/Dm.swift b/Sources/XMTPiOS/Dm.swift index 1ad20c8b..1c1ebdae 100644 --- a/Sources/XMTPiOS/Dm.swift +++ b/Sources/XMTPiOS/Dm.swift @@ -14,8 +14,8 @@ public struct Dm: Identifiable, Equatable, Hashable { Topic.groupMessage(id).description } - func metadata() throws -> FfiConversationMetadata { - return try ffiConversation.groupMetadata() + func metadata() async throws -> FfiConversationMetadata { + return try await ffiConversation.groupMetadata() } public func sync() async throws { @@ -30,12 +30,12 @@ public struct Dm: Identifiable, Equatable, Hashable { id.hash(into: &hasher) } - public func isCreator() throws -> Bool { - return try metadata().creatorInboxId() == client.inboxID + public func isCreator() async throws -> Bool { + return try await metadata().creatorInboxId() == client.inboxID } - public func creatorInboxId() throws -> String { - return try metadata().creatorInboxId() + public func creatorInboxId() async throws -> String { + return try await metadata().creatorInboxId() } public func addedByInboxId() throws -> String { @@ -253,7 +253,7 @@ public struct Dm: Identifiable, Equatable, Hashable { options.direction = direction - return try ffiConversation.findMessages(opts: options).compactMap { + return try await ffiConversation.findMessages(opts: options).compactMap { ffiMessage in return Message(client: self.client, ffiMessage: ffiMessage) .decodeOrNull() diff --git a/Sources/XMTPiOS/Extensions/Ffi.swift b/Sources/XMTPiOS/Extensions/Ffi.swift index 9ab1334d..3587f779 100644 --- a/Sources/XMTPiOS/Extensions/Ffi.swift +++ b/Sources/XMTPiOS/Extensions/Ffi.swift @@ -10,8 +10,8 @@ extension FfiConversation { Dm(ffiConversation: self, client: client) } - func toConversation(client: Client) throws -> Conversation { - if try conversationType() == .dm { + func toConversation(client: Client) async throws -> Conversation { + if try await conversationType() == .dm { return Conversation.dm(self.dmFromFFI(client: client)) } else { return Conversation.group(self.groupFromFFI(client: client)) diff --git a/Sources/XMTPiOS/Group.swift b/Sources/XMTPiOS/Group.swift index abff2f44..bd11891d 100644 --- a/Sources/XMTPiOS/Group.swift +++ b/Sources/XMTPiOS/Group.swift @@ -36,8 +36,8 @@ public struct Group: Identifiable, Equatable, Hashable { Topic.groupMessage(id).description } - func metadata() throws -> FfiConversationMetadata { - return try ffiGroup.groupMetadata() + func metadata() async throws -> FfiConversationMetadata { + return try await ffiGroup.groupMetadata() } func permissions() throws -> FfiGroupPermissions { @@ -60,8 +60,8 @@ public struct Group: Identifiable, Equatable, Hashable { return try ffiGroup.isActive() } - public func isCreator() throws -> Bool { - return try metadata().creatorInboxId() == client.inboxID + public func isCreator() async throws -> Bool { + return try await metadata().creatorInboxId() == client.inboxID } public func isAdmin(inboxId: String) throws -> Bool { @@ -101,8 +101,8 @@ public struct Group: Identifiable, Equatable, Hashable { try permissions().policySet()) } - public func creatorInboxId() throws -> String { - return try metadata().creatorInboxId() + public func creatorInboxId() async throws -> String { + return try await metadata().creatorInboxId() } public func addedByInboxId() throws -> String { @@ -451,7 +451,7 @@ public struct Group: Identifiable, Equatable, Hashable { options.direction = direction - return try ffiGroup.findMessages(opts: options).compactMap { + return try await ffiGroup.findMessages(opts: options).compactMap { ffiMessage in return Message(client: self.client, ffiMessage: ffiMessage) .decodeOrNull() diff --git a/Tests/XMTPTests/ConversationTests.swift b/Tests/XMTPTests/ConversationTests.swift index 4573d06e..69c81326 100644 --- a/Tests/XMTPTests/ConversationTests.swift +++ b/Tests/XMTPTests/ConversationTests.swift @@ -16,9 +16,9 @@ class ConversationTests: XCTestCase { let dm = try await fixtures.boClient.conversations.findOrCreateDm( with: fixtures.caro.walletAddress) - let sameDm = try fixtures.boClient.findConversationByTopic( + let sameDm = try await fixtures.boClient.findConversationByTopic( topic: dm.topic) - let sameGroup = try fixtures.boClient.findConversationByTopic( + let sameGroup = try await fixtures.boClient.findConversationByTopic( topic: group.topic) XCTAssertEqual(group.id, sameGroup?.id) @@ -79,6 +79,34 @@ class ConversationTests: XCTestCase { XCTAssertEqual(convoCountAllowed, 1) XCTAssertEqual(convoCountDenied, 1) } + + func testCanSyncAllConversationsFiltered() async throws { + let fixtures = try await fixtures() + + let dm = try await fixtures.boClient.conversations.findOrCreateDm( + with: fixtures.caro.walletAddress) + let group = try await fixtures.boClient.conversations.newGroup(with: [ + fixtures.caro.walletAddress + ]) + + let convoCount = try await fixtures.boClient.conversations + .syncAllConversations() + let convoCountConsent = try await fixtures.boClient.conversations + .syncAllConversations(consentState: .allowed) + + XCTAssertEqual(convoCount, 2) + XCTAssertEqual(convoCountConsent, 2) + + try await group.updateConsentState(state: .denied) + + let convoCountAllowed = try await fixtures.boClient.conversations + .syncAllConversations(consentState: .allowed) + let convoCountDenied = try await fixtures.boClient.conversations + .syncAllConversations(consentState: .denied) + + XCTAssertEqual(convoCountAllowed, 1) + XCTAssertEqual(convoCountDenied, 1) + } func testCanListConversationsOrder() async throws { let fixtures = try await fixtures() @@ -182,7 +210,7 @@ class ConversationTests: XCTestCase { XCTAssertEqual(try dm.consentState(), .denied) try await fixtures.boClient.conversations.sync() - let boDm = try fixtures.boClient.findConversation(conversationId: dm.id) + let boDm = try await fixtures.boClient.findConversation(conversationId: dm.id) var alixClient2 = try await Client.create( account: alix, @@ -204,7 +232,7 @@ class ConversationTests: XCTestCase { try await alixClient2.conversations.syncAllConversations() sleep(2) - if let dm2 = try alixClient2.findConversation(conversationId: dm.id) { + if let dm2 = try await alixClient2.findConversation(conversationId: dm.id) { XCTAssertEqual(try dm2.consentState(), .denied) try await alixClient2.preferences.setConsentState( diff --git a/Tests/XMTPTests/GroupPermissionsTests.swift b/Tests/XMTPTests/GroupPermissionsTests.swift index 43443c83..1b1ab180 100644 --- a/Tests/XMTPTests/GroupPermissionsTests.swift +++ b/Tests/XMTPTests/GroupPermissionsTests.swift @@ -39,7 +39,8 @@ class GroupPermissionTests: XCTestCase { try boGroup.isAdmin(inboxId: fixtures.boClient.inboxID)) XCTAssertTrue( try boGroup.isSuperAdmin(inboxId: fixtures.boClient.inboxID)) - XCTAssertFalse(try alixGroup.isCreator()) + let isAlixGroupCreator = try await alixGroup.isCreator() + XCTAssertFalse(isAlixGroupCreator) XCTAssertFalse( try alixGroup.isAdmin(inboxId: fixtures.alixClient.inboxID)) XCTAssertFalse( @@ -67,7 +68,8 @@ class GroupPermissionTests: XCTestCase { try boGroup.isAdmin(inboxId: fixtures.boClient.inboxID)) XCTAssertTrue( try boGroup.isSuperAdmin(inboxId: fixtures.boClient.inboxID)) - XCTAssertFalse(try alixGroup.isCreator()) + let isAlixGroupCreator = try await alixGroup.isCreator() + XCTAssertFalse(isAlixGroupCreator) XCTAssertFalse( try alixGroup.isAdmin(inboxId: fixtures.alixClient.inboxID)) XCTAssertFalse( diff --git a/Tests/XMTPTests/GroupTests.swift b/Tests/XMTPTests/GroupTests.swift index 6c43337b..cea21832 100644 --- a/Tests/XMTPTests/GroupTests.swift +++ b/Tests/XMTPTests/GroupTests.swift @@ -179,12 +179,12 @@ class GroupTests: XCTestCase { func testCanListGroupsFiltered() async throws { let fixtures = try await fixtures() - let dm = try await fixtures.boClient.conversations.findOrCreateDm( + let _ = try await fixtures.boClient.conversations.findOrCreateDm( with: fixtures.caro.walletAddress) let group = try await fixtures.boClient.conversations.newGroup(with: [ fixtures.caro.walletAddress ]) - let group2 = try await fixtures.boClient.conversations.newGroup(with: [ + let _ = try await fixtures.boClient.conversations.newGroup(with: [ fixtures.caro.walletAddress ]) @@ -230,9 +230,9 @@ class GroupTests: XCTestCase { XCTAssertEqual(conversationsOrdered.count, 2) XCTAssertEqual( - try conversations.map { try $0.id }, [group1.id, group2.id]) + conversations.map { $0.id }, [group1.id, group2.id]) XCTAssertEqual( - try conversationsOrdered.map { try $0.id }, + conversationsOrdered.map { $0.id }, [group2.id, group1.id]) } diff --git a/XMTP.podspec b/XMTP.podspec index 26ef95cc..15251ba3 100644 --- a/XMTP.podspec +++ b/XMTP.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = "XMTP" - spec.version = "3.0.17" + spec.version = "3.0.18" spec.summary = "XMTP SDK Cocoapod" @@ -23,7 +23,7 @@ Pod::Spec.new do |spec| spec.dependency 'CSecp256k1', '~> 0.2' spec.dependency "Connect-Swift", "= 1.0.0" - spec.dependency 'LibXMTP', '= 3.0.12' + spec.dependency 'LibXMTP', '= 3.0.13' spec.dependency 'CryptoSwift', '= 1.8.3' spec.dependency 'SQLCipher', '= 4.5.7' diff --git a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 80edabbc..7b70aa78 100644 --- a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/xmtp/libxmtp-swift.git", "state" : { - "revision" : "bab83b5de3ed4713d50535e61bca281179bf04fd", - "version" : "3.0.10" + "revision" : "203fd6d67bb72e3114b273ce9bbddd6fc747d583", + "version" : "3.0.13" } }, {