From ede81225aec813acb1061c76ffbb2fcc6b07fea3 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 13 Sep 2024 13:22:24 -0600 Subject: [PATCH 01/10] update package --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a874b2a3..4d3d866a 100644 --- a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/xmtp/libxmtp-swift.git", "state" : { - "revision" : "06e890646a32c3ae9b9ac78150a7ec4971e54c9d", - "version" : "0.5.8-beta3" + "revision" : "abd4f896f539e5bb090c85022177d775ad08dcb1", + "version" : "0.5.8-beta4" } }, { From 6cade8cc0160e9544bca39b44f2cecbc826bfd0a Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 7 Nov 2024 23:04:23 -0800 Subject: [PATCH 02/10] get on the latest version of the sdk --- Package.swift | 2 +- Sources/XMTPiOS/Conversations.swift | 9 +++++++-- XMTP.podspec | 4 ++-- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Package.swift b/Package.swift index 6b39be7d..3e78edad 100644 --- a/Package.swift +++ b/Package.swift @@ -25,7 +25,7 @@ let package = Package( .package(url: "https://github.com/1024jp/GzipSwift", from: "5.2.0"), .package(url: "https://github.com/bufbuild/connect-swift", exact: "0.12.0"), .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.0.0"), - .package(url: "https://github.com/xmtp/libxmtp-swift.git", exact: "0.6.0"), + .package(url: "https://github.com/xmtp/libxmtp-swift.git", exact: "3.0.0"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. diff --git a/Sources/XMTPiOS/Conversations.swift b/Sources/XMTPiOS/Conversations.swift index bf0ffe65..c4addca4 100644 --- a/Sources/XMTPiOS/Conversations.swift +++ b/Sources/XMTPiOS/Conversations.swift @@ -34,6 +34,11 @@ public enum ConversationOrder { case createdAt, lastMessage } + +public enum ConversationType { + case all, group, dm +} + final class ConversationStreamCallback: FfiConversationCallback { func onError(error: LibXMTP.FfiSubscribeError) { print("Error ConversationStreamCallback \(error)") @@ -179,7 +184,7 @@ public actor Conversations { } } - public func stream() -> AsyncThrowingStream< + public func stream(type: ConversationType = .all) -> AsyncThrowingStream< Conversation, Error > { AsyncThrowingStream { continuation in @@ -328,7 +333,7 @@ public actor Conversations { return group } - public func streamAllMessages() -> AsyncThrowingStream< + public func streamAllMessages(type: ConversationType = .all) -> AsyncThrowingStream< DecodedMessage, Error > { AsyncThrowingStream { continuation in diff --git a/XMTP.podspec b/XMTP.podspec index c5f17f92..947da570 100644 --- a/XMTP.podspec +++ b/XMTP.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |spec| # spec.name = "XMTP" - spec.version = "0.16.2" + spec.version = "3.0.1" spec.summary = "XMTP SDK Cocoapod" # This description is used to generate tags and improve search results. @@ -44,5 +44,5 @@ Pod::Spec.new do |spec| spec.dependency "web3.swift" spec.dependency "GzipSwift" spec.dependency "Connect-Swift", "= 0.12.0" - spec.dependency 'LibXMTP', '= 0.6.0' + spec.dependency 'LibXMTP', '= 3.0.0' end diff --git a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c4ca3104..b9e4d141 100644 --- a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/xmtp/libxmtp-swift.git", "state" : { - "revision" : "91653cdaf999119f99189178867e32dcc53b11e8", - "version" : "0.6.0" + "revision" : "83bd77f78de03b63cd35405567036875c3ca9b1c", + "version" : "3.0.0" } }, { From 6726f7832a365faa9a7cbec72276c0a9bab90eca Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 7 Nov 2024 23:07:42 -0800 Subject: [PATCH 03/10] get it compiling --- Sources/XMTPiOS/Client.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/XMTPiOS/Client.swift b/Sources/XMTPiOS/Client.swift index 93a2e245..36019cfa 100644 --- a/Sources/XMTPiOS/Client.swift +++ b/Sources/XMTPiOS/Client.swift @@ -268,7 +268,7 @@ public final class Client { accountAddress: address ) ?? generateInboxId(accountAddress: address, nonce: 0) } catch { - inboxId = generateInboxId(accountAddress: address, nonce: 0) + inboxId = try generateInboxId(accountAddress: address, nonce: 0) } return inboxId } @@ -387,7 +387,7 @@ public final class Client { } public func requestMessageHistorySync() async throws { - try await ffiClient.requestHistorySync() + try await ffiClient.sendSyncRequest(kind: .messages) } public func revokeAllOtherInstallations(signingKey: SigningKey) async throws From a4c1a50434e64935d560ff7fecdadb328f44d213 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 10 Nov 2024 09:50:31 -0800 Subject: [PATCH 04/10] update inbox create to take just the env --- Sources/XMTPiOS/Client.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/XMTPiOS/Client.swift b/Sources/XMTPiOS/Client.swift index 36019cfa..173dfb0b 100644 --- a/Sources/XMTPiOS/Client.swift +++ b/Sources/XMTPiOS/Client.swift @@ -256,15 +256,15 @@ public final class Client { } public static func getOrCreateInboxId( - options: ClientOptions, address: String + api: ClientOptions.Api, address: String ) async throws -> String { var inboxId: String do { inboxId = try await getInboxIdForAddress( logger: XMTPLogger(), - host: options.api.env.url, - isSecure: options.api.env.isSecure == true, + host: api.env.url, + isSecure: api.env.isSecure == true, accountAddress: address ) ?? generateInboxId(accountAddress: address, nonce: 0) } catch { From 6e057cd2d4c59bde6e64ac04e3280c2a1c4bb92e Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 10 Nov 2024 09:55:40 -0800 Subject: [PATCH 05/10] update to handle ns --- Sources/XMTPiOS/Conversation.swift | 10 ++++---- Sources/XMTPiOS/DecodedMessage.swift | 34 +++------------------------ Sources/XMTPiOS/Dm.swift | 14 +++++------ Sources/XMTPiOS/Group.swift | 14 +++++------ Sources/XMTPiOS/Libxmtp/Message.swift | 5 ++++ 5 files changed, 26 insertions(+), 51 deletions(-) diff --git a/Sources/XMTPiOS/Conversation.swift b/Sources/XMTPiOS/Conversation.swift index 56c4ee8a..1e6d4fca 100644 --- a/Sources/XMTPiOS/Conversation.swift +++ b/Sources/XMTPiOS/Conversation.swift @@ -4,7 +4,7 @@ import LibXMTP public enum Conversation: Identifiable, Equatable, Hashable { case group(Group) case dm(Dm) - + public static func == (lhs: Conversation, rhs: Conversation) -> Bool { lhs.topic == rhs.topic } @@ -168,19 +168,21 @@ public enum Conversation: Identifiable, Equatable, Hashable { } public func messages( - limit: Int? = nil, before: Date? = nil, after: Date? = nil, + limit: Int? = nil, + beforeNs: Int64? = nil, + afterNs: Int64? = nil, direction: SortDirection? = .descending, deliveryStatus: MessageDeliveryStatus = .all ) async throws -> [DecodedMessage] { switch self { case let .group(group): return try await group.messages( - before: before, after: after, limit: limit, + beforeNs: beforeNs, afterNs: afterNs, limit: limit, direction: direction, deliveryStatus: deliveryStatus ) case let .dm(dm): return try await dm.messages( - before: before, after: after, limit: limit, + beforeNs: beforeNs, afterNs: afterNs, limit: limit, direction: direction, deliveryStatus: deliveryStatus ) } diff --git a/Sources/XMTPiOS/DecodedMessage.swift b/Sources/XMTPiOS/DecodedMessage.swift index eb3cb49b..abe82d4b 100644 --- a/Sources/XMTPiOS/DecodedMessage.swift +++ b/Sources/XMTPiOS/DecodedMessage.swift @@ -13,6 +13,7 @@ public struct DecodedMessage: Sendable { /// When the message was sent public var sent: Date + public var sentNs: Int64 public var client: Client @@ -25,6 +26,7 @@ public struct DecodedMessage: Sendable { encodedContent: EncodedContent, senderAddress: String, sent: Date, + sentNs: Int64, deliveryStatus: MessageDeliveryStatus = .published ) { self.id = id @@ -33,22 +35,7 @@ public struct DecodedMessage: Sendable { self.encodedContent = encodedContent self.senderAddress = senderAddress self.sent = sent - self.deliveryStatus = deliveryStatus - } - - public init( - client: Client, - topic: String, - encodedContent: EncodedContent, - senderAddress: String, - sent: Date, - deliveryStatus: MessageDeliveryStatus = .published - ) { - self.client = client - self.topic = topic - self.encodedContent = encodedContent - self.senderAddress = senderAddress - self.sent = sent + self.sentNs = sentNs self.deliveryStatus = deliveryStatus } @@ -68,18 +55,3 @@ public struct DecodedMessage: Sendable { } } } - -extension DecodedMessage { - public static func preview( - client: Client, topic: String, body: String, senderAddress: String, - sent: Date - ) -> DecodedMessage { - // swiftlint:disable force_try - let encoded = try! TextCodec().encode(content: body, client: client) - // swiftlint:enable force_try - - return DecodedMessage( - client: client, topic: topic, encodedContent: encoded, - senderAddress: senderAddress, sent: sent) - } -} diff --git a/Sources/XMTPiOS/Dm.swift b/Sources/XMTPiOS/Dm.swift index b4abcdd7..fbce9894 100644 --- a/Sources/XMTPiOS/Dm.swift +++ b/Sources/XMTPiOS/Dm.swift @@ -189,8 +189,8 @@ public struct Dm: Identifiable, Equatable, Hashable { } public func messages( - before: Date? = nil, - after: Date? = nil, + beforeNs: Int64? = nil, + afterNs: Int64? = nil, limit: Int? = nil, direction: SortDirection? = .descending, deliveryStatus: MessageDeliveryStatus = .all @@ -203,14 +203,12 @@ public struct Dm: Identifiable, Equatable, Hashable { direction: nil ) - if let before { - options.sentBeforeNs = Int64( - before.millisecondsSinceEpoch * 1_000_000) + if let beforeNs { + options.sentBeforeNs = beforeNs } - if let after { - options.sentAfterNs = Int64( - after.millisecondsSinceEpoch * 1_000_000) + if let afterNs { + options.sentAfterNs = afterNs } if let limit { diff --git a/Sources/XMTPiOS/Group.swift b/Sources/XMTPiOS/Group.swift index f69200fc..63327ec5 100644 --- a/Sources/XMTPiOS/Group.swift +++ b/Sources/XMTPiOS/Group.swift @@ -387,8 +387,8 @@ public struct Group: Identifiable, Equatable, Hashable { } public func messages( - before: Date? = nil, - after: Date? = nil, + beforeNs: Int64? = nil, + afterNs: Int64? = nil, limit: Int? = nil, direction: SortDirection? = .descending, deliveryStatus: MessageDeliveryStatus = .all @@ -401,14 +401,12 @@ public struct Group: Identifiable, Equatable, Hashable { direction: nil ) - if let before { - options.sentBeforeNs = Int64( - before.millisecondsSinceEpoch * 1_000_000) + if let beforeNs { + options.sentBeforeNs = beforeNs } - if let after { - options.sentAfterNs = Int64( - after.millisecondsSinceEpoch * 1_000_000) + if let afterNs { + options.sentAfterNs = afterNs } if let limit { diff --git a/Sources/XMTPiOS/Libxmtp/Message.swift b/Sources/XMTPiOS/Libxmtp/Message.swift index 69302593..5ff214b3 100644 --- a/Sources/XMTPiOS/Libxmtp/Message.swift +++ b/Sources/XMTPiOS/Libxmtp/Message.swift @@ -42,6 +42,10 @@ public struct Message: Identifiable { timeIntervalSince1970: TimeInterval(ffiMessage.sentAtNs) / 1_000_000_000) } + + var sentAtNs: Int64 { + return ffiMessage.sentAtNs + } var deliveryStatus: MessageDeliveryStatus { switch ffiMessage.deliveryStatus { @@ -66,6 +70,7 @@ public struct Message: Identifiable { encodedContent: encodedContent, senderAddress: senderInboxId, sent: sentAt, + sentNs: sentAtNs, deliveryStatus: deliveryStatus ) From c98d24cb6fd31e07996d511779d148fcfb1bca4e Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 10 Nov 2024 09:56:58 -0800 Subject: [PATCH 06/10] add publish messages to the common interface --- Sources/XMTPiOS/Conversation.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Sources/XMTPiOS/Conversation.swift b/Sources/XMTPiOS/Conversation.swift index 1e6d4fca..b4771888 100644 --- a/Sources/XMTPiOS/Conversation.swift +++ b/Sources/XMTPiOS/Conversation.swift @@ -92,6 +92,15 @@ public enum Conversation: Identifiable, Equatable, Hashable { content: content, options: options) } } + + public func publishMessages() async throws { + switch self { + case let .group(group): + return try await group.publishMessages() + case let .dm(dm): + return try await dm.publishMessages() + } + } public var type: ConversationType { switch self { From f21fde5bb5cc84e25c6b669a8116cf71f17cf1fe Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 10 Nov 2024 10:00:54 -0800 Subject: [PATCH 07/10] update the listing functions --- Sources/XMTPiOS/Conversations.swift | 40 +++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/Sources/XMTPiOS/Conversations.swift b/Sources/XMTPiOS/Conversations.swift index c4addca4..72c355a5 100644 --- a/Sources/XMTPiOS/Conversations.swift +++ b/Sources/XMTPiOS/Conversations.swift @@ -34,7 +34,6 @@ public enum ConversationOrder { case createdAt, lastMessage } - public enum ConversationType { case all, group, dm } @@ -85,11 +84,13 @@ public actor Conversations { } public func listGroups( - createdAfter: Date? = nil, createdBefore: Date? = nil, limit: Int? = nil + createdAfter: Date? = nil, createdBefore: Date? = nil, + limit: Int? = nil, order: ConversationOrder = .createdAt, + consentState: ConsentState? = nil ) async throws -> [Group] { var options = FfiListConversationsOptions( createdAfterNs: nil, createdBeforeNs: nil, limit: nil, - consentState: nil) + consentState: consentState?.toFFI) if let createdAfter { options.createdAfterNs = Int64(createdAfter.millisecondsSinceEpoch) } @@ -100,17 +101,25 @@ public actor Conversations { if let limit { options.limit = Int64(limit) } - return try await ffiConversations.listGroups(opts: options).map { + let conversations = try await ffiConversations.listGroups( + opts: options) + + let sortedConversations = try sortConversations( + conversations, order: order) + + return sortedConversations.map { $0.groupFromFFI(client: client) } } public func listDms( - createdAfter: Date? = nil, createdBefore: Date? = nil, limit: Int? = nil + createdAfter: Date? = nil, createdBefore: Date? = nil, + limit: Int? = nil, order: ConversationOrder = .createdAt, + consentState: ConsentState? = nil ) async throws -> [Dm] { var options = FfiListConversationsOptions( createdAfterNs: nil, createdBeforeNs: nil, limit: nil, - consentState: nil) + consentState: consentState?.toFFI) if let createdAfter { options.createdAfterNs = Int64(createdAfter.millisecondsSinceEpoch) } @@ -121,7 +130,13 @@ public actor Conversations { if let limit { options.limit = Int64(limit) } - return try await ffiConversations.listDms(opts: options).map { + let conversations = try await ffiConversations.listDms( + opts: options) + + let sortedConversations = try sortConversations( + conversations, order: order) + + return sortedConversations.map { $0.dmFromFFI(client: client) } } @@ -311,7 +326,8 @@ public actor Conversations { throw ConversationError.memberCannotBeSelf } let addressMap = try await self.client.canMessage(addresses: addresses) - let unregisteredAddresses = addressMap + let unregisteredAddresses = + addressMap .filter { !$0.value } .map { $0.key } @@ -333,9 +349,11 @@ public actor Conversations { return group } - public func streamAllMessages(type: ConversationType = .all) -> AsyncThrowingStream< - DecodedMessage, Error - > { + public func streamAllMessages(type: ConversationType = .all) + -> AsyncThrowingStream< + DecodedMessage, Error + > + { AsyncThrowingStream { continuation in let ffiStreamActor = FfiStreamActor() let task = Task { From ce1a6a87f81e4833533f25238b918eb40cc78916 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 10 Nov 2024 10:07:50 -0800 Subject: [PATCH 08/10] switch on conversation type --- Sources/XMTPiOS/Client.swift | 8 +- Sources/XMTPiOS/Conversations.swift | 122 ++++++++++++++++------------ 2 files changed, 77 insertions(+), 53 deletions(-) diff --git a/Sources/XMTPiOS/Client.swift b/Sources/XMTPiOS/Client.swift index 173dfb0b..dffac1ae 100644 --- a/Sources/XMTPiOS/Client.swift +++ b/Sources/XMTPiOS/Client.swift @@ -140,7 +140,7 @@ public final class Client { { let accountAddress = account.address.lowercased() let inboxId = try await getOrCreateInboxId( - options: options, address: accountAddress) + api: options.api, address: accountAddress) return try await initializeClient( accountAddress: accountAddress, @@ -155,7 +155,7 @@ public final class Client { { let accountAddress = address.lowercased() let inboxId = try await getOrCreateInboxId( - options: options, address: accountAddress) + api: options.api, address: accountAddress) return try await initializeClient( accountAddress: accountAddress, @@ -390,6 +390,10 @@ public final class Client { try await ffiClient.sendSyncRequest(kind: .messages) } + public func syncConsent() async throws { + try await ffiClient.sendSyncRequest(kind: .consent) + } + public func revokeAllOtherInstallations(signingKey: SigningKey) async throws { let signatureRequest = try await ffiClient.revokeAllOtherInstallations() diff --git a/Sources/XMTPiOS/Conversations.swift b/Sources/XMTPiOS/Conversations.swift index 72c355a5..08bd4419 100644 --- a/Sources/XMTPiOS/Conversations.swift +++ b/Sources/XMTPiOS/Conversations.swift @@ -204,35 +204,44 @@ public actor Conversations { > { AsyncThrowingStream { continuation in let ffiStreamActor = FfiStreamActor() + let conversationCallback = ConversationStreamCallback { + conversation in + guard !Task.isCancelled else { + continuation.finish() + return + } + do { + let conversationType = try conversation.groupMetadata() + .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 { + // Do nothing if the conversation type is neither a group or dm + } + } + let task = Task { - let stream = await ffiConversations.stream( - callback: ConversationStreamCallback { conversation in - guard !Task.isCancelled else { - continuation.finish() - return - } - do { - let conversationType = - try conversation.groupMetadata() - .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 { - // Do nothing if the conversation type is neither a group or dm - } + let stream = + switch type { + case .group: + await ffiConversations.streamGroups( + callback: conversationCallback) + case .all: + await ffiConversations.stream( + callback: conversationCallback) + case .dm: + await ffiConversations.streamDms( + callback: conversationCallback) } - ) await ffiStreamActor.setFfiStream(stream) continuation.onTermination = { @Sendable reason in Task { @@ -350,35 +359,46 @@ public actor Conversations { } public func streamAllMessages(type: ConversationType = .all) - -> AsyncThrowingStream< - DecodedMessage, Error - > + -> AsyncThrowingStream { AsyncThrowingStream { continuation in let ffiStreamActor = FfiStreamActor() + + let messageCallback = MessageCallback(client: self.client) { + message in + guard !Task.isCancelled else { + continuation.finish() + Task { + await ffiStreamActor.endStream() + } + return + } + do { + continuation.yield( + try Message(client: self.client, ffiMessage: message) + .decode() + ) + } catch { + print("Error onMessage \(error)") + } + } + let task = Task { let stream = - await ffiConversations - .streamAllMessages( - messageCallback: MessageCallback(client: self.client) { - message in - guard !Task.isCancelled else { - continuation.finish() - Task { - await ffiStreamActor.endStream() // End the stream upon cancellation - } - return - } - do { - continuation.yield( - try Message( - client: self.client, ffiMessage: message - ).decode()) - } catch { - print("Error onMessage \(error)") - } - } - ) + switch type { + case .group: + await ffiConversations.streamAllGroupMessages( + messageCallback: messageCallback + ) + case .dm: + await ffiConversations.streamAllDmMessages( + messageCallback: messageCallback + ) + case .all: + await ffiConversations.streamAllMessages( + messageCallback: messageCallback + ) + } await ffiStreamActor.setFfiStream(stream) } From cba9c4ede2525bad4c2c2c2be79026a0fc16e2b1 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 10 Nov 2024 10:29:08 -0800 Subject: [PATCH 09/10] add tests for everything --- Sources/XMTPiOS/Conversation.swift | 2 +- Sources/XMTPiOS/Conversations.swift | 10 +- Tests/XMTPTests/ClientTests.swift | 20 +-- Tests/XMTPTests/ConversationTests.swift | 224 ++++++++++++++++++++++++ Tests/XMTPTests/DmTests.swift | 86 ++++++++- Tests/XMTPTests/GroupTests.swift | 149 ++-------------- 6 files changed, 331 insertions(+), 160 deletions(-) create mode 100644 Tests/XMTPTests/ConversationTests.swift diff --git a/Sources/XMTPiOS/Conversation.swift b/Sources/XMTPiOS/Conversation.swift index b4771888..672cc6b0 100644 --- a/Sources/XMTPiOS/Conversation.swift +++ b/Sources/XMTPiOS/Conversation.swift @@ -44,7 +44,7 @@ public enum Conversation: Identifiable, Equatable, Hashable { } } - public func consentState() async throws -> ConsentState { + public func consentState() throws -> ConsentState { switch self { case let .group(group): return try group.consentState() diff --git a/Sources/XMTPiOS/Conversations.swift b/Sources/XMTPiOS/Conversations.swift index 08bd4419..06e9f6c9 100644 --- a/Sources/XMTPiOS/Conversations.swift +++ b/Sources/XMTPiOS/Conversations.swift @@ -35,7 +35,7 @@ public enum ConversationOrder { } public enum ConversationType { - case all, group, dm + case all, groups, dms } final class ConversationStreamCallback: FfiConversationCallback { @@ -232,13 +232,13 @@ public actor Conversations { let task = Task { let stream = switch type { - case .group: + case .groups: await ffiConversations.streamGroups( callback: conversationCallback) case .all: await ffiConversations.stream( callback: conversationCallback) - case .dm: + case .dms: await ffiConversations.streamDms( callback: conversationCallback) } @@ -386,11 +386,11 @@ public actor Conversations { let task = Task { let stream = switch type { - case .group: + case .groups: await ffiConversations.streamAllGroupMessages( messageCallback: messageCallback ) - case .dm: + case .dms: await ffiConversations.streamAllDmMessages( messageCallback: messageCallback ) diff --git a/Tests/XMTPTests/ClientTests.swift b/Tests/XMTPTests/ClientTests.swift index 040ecdef..d7c8191c 100644 --- a/Tests/XMTPTests/ClientTests.swift +++ b/Tests/XMTPTests/ClientTests.swift @@ -250,24 +250,6 @@ class ClientTests: XCTestCase { XCTAssertEqual(boClient.inboxID, boInboxId) } - func testCreatesAV3Client() async throws { - let key = try Crypto.secureRandomBytes(count: 32) - let alix = try PrivateKey.generate() - let options = ClientOptions.init( - api: .init(env: .local, isSecure: false), - dbEncryptionKey: key - ) - - let inboxId = try await Client.getOrCreateInboxId( - options: options, address: alix.address) - let alixClient = try await Client.create( - account: alix, - options: options - ) - - XCTAssertEqual(inboxId, alixClient.inboxID) - } - func testCreatesAClient() async throws { let key = try Crypto.secureRandomBytes(count: 32) let alix = try PrivateKey.generate() @@ -277,7 +259,7 @@ class ClientTests: XCTestCase { ) let inboxId = try await Client.getOrCreateInboxId( - options: options, address: alix.address) + api: options.api, address: alix.address) let alixClient = try await Client.create( account: alix, options: options diff --git a/Tests/XMTPTests/ConversationTests.swift b/Tests/XMTPTests/ConversationTests.swift new file mode 100644 index 00000000..f5cdf179 --- /dev/null +++ b/Tests/XMTPTests/ConversationTests.swift @@ -0,0 +1,224 @@ +import CryptoKit +import LibXMTP +import XCTest +import XMTPTestHelpers + +@testable import XMTPiOS + +@available(iOS 16, *) +class ConversationTests: XCTestCase { + func testCanFindConversationByTopic() async throws { + let fixtures = try await fixtures() + + let group = try await fixtures.boClient.conversations.newGroup(with: [ + fixtures.caro.walletAddress + ]) + let dm = try await fixtures.boClient.conversations.findOrCreateDm( + with: fixtures.caro.walletAddress) + + let sameDm = try fixtures.boClient.findConversationByTopic( + topic: dm.topic) + let sameGroup = try fixtures.boClient.findConversationByTopic( + topic: group.topic) + + XCTAssertEqual(group.id, sameGroup?.id) + XCTAssertEqual(dm.id, sameDm?.id) + } + + func testCanListConversations() 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 + .list().count + let dmCount = try await fixtures.boClient.conversations.listDms().count + let groupCount = try await fixtures.boClient.conversations.listGroups() + .count + XCTAssertEqual(convoCount, 2) + XCTAssertEqual(dmCount, 1) + XCTAssertEqual(groupCount, 1) + + try await fixtures.caroClient.conversations.sync() + let convoCount2 = try await fixtures.caroClient.conversations.list() + .count + let groupCount2 = try await fixtures.caroClient.conversations + .listGroups().count + XCTAssertEqual(convoCount2, 2) + XCTAssertEqual(groupCount2, 1) + } + + func testCanListConversationsFiltered() 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 + .list().count + let convoCountConsent = try await fixtures.boClient.conversations + .list(consentState: .allowed).count + + XCTAssertEqual(convoCount, 2) + XCTAssertEqual(convoCountConsent, 2) + + try await group.updateConsentState(state: .denied) + + let convoCountAllowed = try await fixtures.boClient.conversations + .list(consentState: .allowed).count + let convoCountDenied = try await fixtures.boClient.conversations + .list(consentState: .denied).count + + XCTAssertEqual(convoCountAllowed, 1) + XCTAssertEqual(convoCountDenied, 1) + } + + func testCanListConversationsOrder() async throws { + let fixtures = try await fixtures() + + let dm = try await fixtures.boClient.conversations.findOrCreateDm( + with: fixtures.caro.walletAddress) + let group1 = try await fixtures.boClient.conversations.newGroup( + with: [fixtures.caro.walletAddress]) + let group2 = try await fixtures.boClient.conversations.newGroup( + with: [fixtures.caro.walletAddress]) + + _ = try await dm.send(content: "Howdy") + _ = try await group2.send(content: "Howdy") + _ = try await fixtures.boClient.conversations.syncAllConversations() + + let conversations = try await fixtures.boClient.conversations + .list() + let conversationsOrdered = try await fixtures.boClient.conversations + .list(order: .lastMessage) + + XCTAssertEqual(conversations.count, 3) + XCTAssertEqual(conversationsOrdered.count, 3) + + XCTAssertEqual( + conversations.map { $0.id }, [dm.id, group1.id, group2.id]) + XCTAssertEqual( + conversationsOrdered.map { $0.id }, + [group2.id, dm.id, group1.id]) + } + + func testCanStreamConversations() async throws { + let fixtures = try await fixtures() + + let expectation1 = XCTestExpectation(description: "got a conversation") + expectation1.expectedFulfillmentCount = 2 + + Task(priority: .userInitiated) { + for try await _ in await fixtures.alixClient.conversations.stream() + { + expectation1.fulfill() + } + } + + _ = try await fixtures.boClient.conversations.newGroup(with: [ + fixtures.alix.address + ]) + _ = try await fixtures.boClient.conversations.newConversation( + with: fixtures.alix.address) + _ = try await fixtures.caroClient.conversations.findOrCreateDm( + with: fixtures.alix.address) + + await fulfillment(of: [expectation1], timeout: 3) + } + + func testCanStreamAllMessages() async throws { + let fixtures = try await fixtures() + + let expectation1 = XCTestExpectation(description: "got a conversation") + expectation1.expectedFulfillmentCount = 2 + let convo = try await fixtures.boClient.conversations.newConversation( + with: fixtures.alix.address) + let group = try await fixtures.boClient.conversations.newGroup(with: [ + fixtures.alix.address + ]) + let dm = try await fixtures.caroClient.conversations.findOrCreateDm( + with: fixtures.alix.address) + + try await fixtures.alixClient.conversations.sync() + Task(priority: .userInitiated) { + for try await _ in await fixtures.alixClient.conversations + .streamAllMessages() + { + expectation1.fulfill() + } + } + + _ = try await group.send(content: "hi") + _ = try await convo.send(content: "hi") + _ = try await dm.send(content: "hi") + + await fulfillment(of: [expectation1], timeout: 3) + } + + func testSyncConsent() async throws { + let fixtures = try await fixtures() + + let key = try Crypto.secureRandomBytes(count: 32) + let alix = try PrivateKey.generate() + var alixClient = try await Client.create( + account: alix, + options: .init( + api: .init(env: .local, isSecure: false), + dbEncryptionKey: key, + dbDirectory: "xmtp_db" + ) + ) + + let dm = try await alixClient.conversations.findOrCreateDm( + with: fixtures.bo.walletAddress) + try await dm.updateConsentState(state: .denied) + XCTAssertEqual(try dm.consentState(), .denied) + + try await fixtures.boClient.conversations.sync() + let boDm = try fixtures.boClient.findConversation(conversationId: dm.id) + + var alixClient2 = try await Client.create( + account: alix, + options: .init( + api: .init(env: .local, isSecure: false), + dbEncryptionKey: key, + dbDirectory: "xmtp_db2" + ) + ) + + let state = try await alixClient2.inboxState(refreshFromNetwork: true) + XCTAssertEqual(state.installations.count, 2) + + try await fixtures.boClient.conversations.sync() + try await boDm?.sync() + try await alixClient2.conversations.sync() + + if let dm2 = try alixClient2.findConversation(conversationId: dm.id) { + try await alixClient2.syncConsent() + XCTAssertEqual(try dm2.consentState(), .denied) + + try await alixClient2.preferences.consentList.setConsentState( + entries: [ + ConsentListEntry( + value: dm2.id, + entryType: .conversation_id, + consentType: .allowed + ) + ] + ) + let convoState = try await alixClient2.preferences.consentList + .conversationState( + conversationId: dm2.id) + XCTAssertEqual(convoState, .allowed) + XCTAssertEqual(try dm2.consentState(), .allowed) + } + } + +} diff --git a/Tests/XMTPTests/DmTests.swift b/Tests/XMTPTests/DmTests.swift index 81adc6e7..d1ef6c0b 100644 --- a/Tests/XMTPTests/DmTests.swift +++ b/Tests/XMTPTests/DmTests.swift @@ -64,6 +64,65 @@ class DmTests: XCTestCase { XCTAssertEqual(dmState, .allowed) XCTAssertEqual(try dm.consentState(), .allowed) } + + func testCanListDmsFiltered() async throws { + let fixtures = try await fixtures() + + let dm = try await fixtures.boClient.conversations.findOrCreateDm( + with: fixtures.caro.walletAddress) + let dm2 = try await fixtures.boClient.conversations.findOrCreateDm( + with: fixtures.alix.walletAddress) + let group = try await fixtures.boClient.conversations.newGroup(with: [ + fixtures.caro.walletAddress + ]) + + let convoCount = try await fixtures.boClient.conversations + .listDms().count + let convoCountConsent = try await fixtures.boClient.conversations + .listDms(consentState: .allowed).count + + XCTAssertEqual(convoCount, 2) + XCTAssertEqual(convoCountConsent, 2) + + try await dm2.updateConsentState(state: .denied) + + let convoCountAllowed = try await fixtures.boClient.conversations + .listDms(consentState: .allowed).count + let convoCountDenied = try await fixtures.boClient.conversations + .listDms(consentState: .denied).count + + XCTAssertEqual(convoCountAllowed, 1) + XCTAssertEqual(convoCountDenied, 1) + } + + func testCanListConversationsOrder() async throws { + let fixtures = try await fixtures() + + let dm = try await fixtures.boClient.conversations.findOrCreateDm( + with: fixtures.caro.walletAddress) + let dm2 = try await fixtures.boClient.conversations.findOrCreateDm( + with: fixtures.alix.walletAddress) + let group2 = try await fixtures.boClient.conversations.newGroup( + with: [fixtures.caro.walletAddress]) + + _ = try await dm.send(content: "Howdy") + _ = try await dm2.send(content: "Howdy") + _ = try await fixtures.boClient.conversations.syncAllConversations() + + let conversations = try await fixtures.boClient.conversations + .listDms() + let conversationsOrdered = try await fixtures.boClient.conversations + .listDms(order: .lastMessage) + + XCTAssertEqual(conversations.count, 2) + XCTAssertEqual(conversationsOrdered.count, 2) + + XCTAssertEqual( + try conversations.map { try $0.id }, [dm.id, dm2.id]) + XCTAssertEqual( + try conversationsOrdered.map { try $0.id }, + [dm2.id, dm.id]) + } func testCanSendMessageToDm() async throws { let fixtures = try await fixtures() @@ -110,8 +169,31 @@ class DmTests: XCTestCase { await fulfillment(of: [expectation1], timeout: 3) } + + func testCanStreamDms() async throws { + let fixtures = try await fixtures() + + let expectation1 = XCTestExpectation(description: "got a group") + expectation1.expectedFulfillmentCount = 1 + + Task(priority: .userInitiated) { + for try await _ in await fixtures.alixClient.conversations + .stream(type: .dms) + { + expectation1.fulfill() + } + } + + _ = try await fixtures.boClient.conversations.newGroup(with: [ + fixtures.alix.address + ]) + _ = try await fixtures.caroClient.conversations.findOrCreateDm( + with: fixtures.alix.address) + + await fulfillment(of: [expectation1], timeout: 3) + } - func testCanStreamAllDecryptedDmMessages() async throws { + func testCanStreamAllDmMessages() async throws { let fixtures = try await fixtures() let dm = try await fixtures.boClient.conversations.findOrCreateDm( @@ -123,7 +205,7 @@ class DmTests: XCTestCase { Task(priority: .userInitiated) { for try await _ in await fixtures.alixClient.conversations - .streamAllMessages() + .streamAllMessages(type: .dms) { expectation1.fulfill() } diff --git a/Tests/XMTPTests/GroupTests.swift b/Tests/XMTPTests/GroupTests.swift index c4050a9a..82fbf816 100644 --- a/Tests/XMTPTests/GroupTests.swift +++ b/Tests/XMTPTests/GroupTests.swift @@ -177,25 +177,7 @@ class GroupTests: XCTestCase { XCTAssertEqual(1, boGroupCount) } - func testCanFindConversationByTopic() async throws { - let fixtures = try await fixtures() - - let group = try await fixtures.boClient.conversations.newGroup(with: [ - fixtures.caro.walletAddress - ]) - let dm = try await fixtures.boClient.conversations.findOrCreateDm( - with: fixtures.caro.walletAddress) - - let sameDm = try fixtures.boClient.findConversationByTopic( - topic: dm.topic) - let sameGroup = try fixtures.boClient.findConversationByTopic( - topic: group.topic) - - XCTAssertEqual(group.id, try sameGroup?.id) - XCTAssertEqual(dm.id, try sameDm?.id) - } - - func testCanListConversations() async throws { + func testCanListGroupsFiltered() async throws { let fixtures = try await fixtures() let dm = try await fixtures.boClient.conversations.findOrCreateDm( @@ -203,38 +185,14 @@ class GroupTests: XCTestCase { let group = try await fixtures.boClient.conversations.newGroup(with: [ fixtures.caro.walletAddress ]) - - let convoCount = try await fixtures.boClient.conversations - .list().count - let dmCount = try await fixtures.boClient.conversations.listDms().count - let groupCount = try await fixtures.boClient.conversations.listGroups() - .count - XCTAssertEqual(convoCount, 2) - XCTAssertEqual(dmCount, 1) - XCTAssertEqual(groupCount, 1) - - try await fixtures.caroClient.conversations.sync() - let convoCount2 = try await fixtures.caroClient.conversations.list() - .count - let groupCount2 = try await fixtures.caroClient.conversations - .listGroups().count - XCTAssertEqual(convoCount2, 2) - XCTAssertEqual(groupCount2, 1) - } - - func testCanListConversationsFiltered() 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: [ + let group2 = try await fixtures.boClient.conversations.newGroup(with: [ fixtures.caro.walletAddress ]) let convoCount = try await fixtures.boClient.conversations - .list().count + .listGroups().count let convoCountConsent = try await fixtures.boClient.conversations - .list(consentState: .allowed).count + .listGroups(consentState: .allowed).count XCTAssertEqual(convoCount, 2) XCTAssertEqual(convoCountConsent, 2) @@ -242,15 +200,15 @@ class GroupTests: XCTestCase { try await group.updateConsentState(state: .denied) let convoCountAllowed = try await fixtures.boClient.conversations - .list(consentState: .allowed).count + .listGroups(consentState: .allowed).count let convoCountDenied = try await fixtures.boClient.conversations - .list(consentState: .denied).count + .listGroups(consentState: .denied).count XCTAssertEqual(convoCountAllowed, 1) XCTAssertEqual(convoCountDenied, 1) } - func testCanListConversationsOrder() async throws { + func testCanListGroupsOrder() async throws { let fixtures = try await fixtures() let dm = try await fixtures.boClient.conversations.findOrCreateDm( @@ -265,41 +223,18 @@ class GroupTests: XCTestCase { _ = try await fixtures.boClient.conversations.syncAllConversations() let conversations = try await fixtures.boClient.conversations - .list() + .listGroups() let conversationsOrdered = try await fixtures.boClient.conversations - .list(order: .lastMessage) + .listGroups(order: .lastMessage) - XCTAssertEqual(conversations.count, 3) - XCTAssertEqual(conversationsOrdered.count, 3) + XCTAssertEqual(conversations.count, 2) + XCTAssertEqual(conversationsOrdered.count, 2) XCTAssertEqual( - try conversations.map { try $0.id }, [dm.id, group1.id, group2.id]) + try conversations.map { try $0.id }, [group1.id, group2.id]) XCTAssertEqual( try conversationsOrdered.map { try $0.id }, - [group2.id, dm.id, group1.id]) - } - - func testCanListGroupsAndConversations() async throws { - let fixtures = try await fixtures() - _ = try await fixtures.alixClient.conversations.newGroup(with: [ - fixtures.bo.address - ]) - _ = try await fixtures.alixClient.conversations.newConversation( - with: fixtures.bo.address) - _ = try await fixtures.caroClient.conversations.findOrCreateDm( - with: fixtures.bo.walletAddress) - _ = try await fixtures.caroClient.conversations.findOrCreateDm( - with: fixtures.alix.walletAddress) - - let alixGroupCount = try await fixtures.alixClient.conversations - .list().count - - try await fixtures.boClient.conversations.sync() - let boGroupCount = try await fixtures.boClient.conversations.list() - .count - - XCTAssertEqual(2, alixGroupCount) - XCTAssertEqual(3, boGroupCount) + [group2.id, group1.id]) } func testCanListGroupMembers() async throws { @@ -679,10 +614,11 @@ class GroupTests: XCTestCase { let fixtures = try await fixtures() let expectation1 = XCTestExpectation(description: "got a group") + expectation1.expectedFulfillmentCount = 1 Task(priority: .userInitiated) { for try await _ in await fixtures.alixClient.conversations - .stream() + .stream(type: .groups) { expectation1.fulfill() } @@ -697,30 +633,6 @@ class GroupTests: XCTestCase { await fulfillment(of: [expectation1], timeout: 3) } - func testCanStreamGroupsAndConversationsWorksGroups() async throws { - let fixtures = try await fixtures() - - let expectation1 = XCTestExpectation(description: "got a conversation") - expectation1.expectedFulfillmentCount = 2 - - Task(priority: .userInitiated) { - for try await _ in await fixtures.alixClient.conversations.stream() - { - expectation1.fulfill() - } - } - - _ = try await fixtures.boClient.conversations.newGroup(with: [ - fixtures.alix.address - ]) - _ = try await fixtures.boClient.conversations.newConversation( - with: fixtures.alix.address) - _ = try await fixtures.caroClient.conversations.findOrCreateDm( - with: fixtures.alix.address) - - await fulfillment(of: [expectation1], timeout: 3) - } - func testStreamGroupsAndAllMessages() async throws { let fixtures = try await fixtures() @@ -815,35 +727,6 @@ class GroupTests: XCTestCase { await fulfillment(of: [expectation], timeout: 3) } - func testCanStreamAllMessages() async throws { - let fixtures = try await fixtures() - - let expectation1 = XCTestExpectation(description: "got a conversation") - expectation1.expectedFulfillmentCount = 2 - let convo = try await fixtures.boClient.conversations.newConversation( - with: fixtures.alix.address) - let group = try await fixtures.boClient.conversations.newGroup(with: [ - fixtures.alix.address - ]) - let dm = try await fixtures.caroClient.conversations.findOrCreateDm( - with: fixtures.alix.address) - - try await fixtures.alixClient.conversations.sync() - Task(priority: .userInitiated) { - for try await _ in await fixtures.alixClient.conversations - .streamAllMessages() - { - expectation1.fulfill() - } - } - - _ = try await group.send(content: "hi") - _ = try await convo.send(content: "hi") - _ = try await dm.send(content: "hi") - - await fulfillment(of: [expectation1], timeout: 3) - } - func testCanStreamAllGroupMessages() async throws { let fixtures = try await fixtures() @@ -857,7 +740,7 @@ class GroupTests: XCTestCase { try await fixtures.alixClient.conversations.sync() Task(priority: .userInitiated) { for try await _ in await fixtures.alixClient.conversations - .streamAllMessages() + .streamAllMessages(type: .groups) { expectation1.fulfill() } From 4ebfa8b2e138227084c0a39cc94e88ea67bfdac6 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 10 Nov 2024 10:39:55 -0800 Subject: [PATCH 10/10] fix up lint issue --- Sources/XMTPiOS/Conversations.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/XMTPiOS/Conversations.swift b/Sources/XMTPiOS/Conversations.swift index 06e9f6c9..9ad18ea4 100644 --- a/Sources/XMTPiOS/Conversations.swift +++ b/Sources/XMTPiOS/Conversations.swift @@ -230,16 +230,16 @@ public actor Conversations { } let task = Task { - let stream = + let stream: FfiStreamCloser switch type { case .groups: - await ffiConversations.streamGroups( + stream = await ffiConversations.streamGroups( callback: conversationCallback) case .all: - await ffiConversations.stream( + stream = await ffiConversations.stream( callback: conversationCallback) case .dms: - await ffiConversations.streamDms( + stream = await ffiConversations.streamDms( callback: conversationCallback) } await ffiStreamActor.setFfiStream(stream) @@ -384,18 +384,18 @@ public actor Conversations { } let task = Task { - let stream = + let stream: FfiStreamCloser switch type { case .groups: - await ffiConversations.streamAllGroupMessages( + stream = await ffiConversations.streamAllGroupMessages( messageCallback: messageCallback ) case .dms: - await ffiConversations.streamAllDmMessages( + stream = await ffiConversations.streamAllDmMessages( messageCallback: messageCallback ) case .all: - await ffiConversations.streamAllMessages( + stream = await ffiConversations.streamAllMessages( messageCallback: messageCallback ) }