From dbb39bc95fe79eb218317b64d9cac4972c32887f Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Tue, 19 Sep 2023 08:36:20 -0700 Subject: [PATCH] Make Conversations and Contacts actors. (#161) * Make Conversations and Contacts actors. Hopefully will fix https://github.com/xmtp/xmtp-ios/issues/159. * bump podspec --- Sources/XMTP/Contacts.swift | 12 ++++- Sources/XMTP/ConversationV1.swift | 4 +- Sources/XMTP/Conversations.swift | 2 +- Tests/XMTPTests/ContactsTests.swift | 3 +- Tests/XMTPTests/ConversationTests.swift | 70 +------------------------ Tests/XMTPTests/ConversationsTest.swift | 4 +- Tests/XMTPTests/IntegrationTests.swift | 4 +- Tests/XMTPTests/PaginationTests.swift | 2 +- XMTP.podspec | 2 +- 9 files changed, 22 insertions(+), 81 deletions(-) diff --git a/Sources/XMTP/Contacts.swift b/Sources/XMTP/Contacts.swift index 9cadfe34..1e750628 100644 --- a/Sources/XMTP/Contacts.swift +++ b/Sources/XMTP/Contacts.swift @@ -8,7 +8,7 @@ import Foundation /// Provides access to contact bundles. -public struct Contacts { +public actor Contacts { var client: Client // Save all bundles here @@ -17,6 +17,14 @@ public struct Contacts { // Whether or not we have sent invite/intro to this contact var hasIntroduced: [String: Bool] = [:] + init(client: Client) { + self.client = client + } + + func markIntroduced(_ peerAddress: String, _ isIntroduced: Bool) { + hasIntroduced[peerAddress] = isIntroduced + } + func has(_ peerAddress: String) -> Bool { return knownBundles[peerAddress] != nil } @@ -25,7 +33,7 @@ public struct Contacts { return hasIntroduced[peerAddress] != true } - mutating func find(_ peerAddress: String) async throws -> ContactBundle? { + func find(_ peerAddress: String) async throws -> ContactBundle? { if let knownBundle = knownBundles[peerAddress] { return knownBundle } diff --git a/Sources/XMTP/ConversationV1.swift b/Sources/XMTP/ConversationV1.swift index f35ba789..8bb922d2 100644 --- a/Sources/XMTP/ConversationV1.swift +++ b/Sources/XMTP/ConversationV1.swift @@ -71,7 +71,7 @@ public struct ConversationV1 { message: msg ) var envelopes = [messageEnvelope] - if client.contacts.needsIntroduction(peerAddress) && !isEphemeral { + if (await client.contacts.needsIntroduction(peerAddress)) && !isEphemeral { envelopes.append(contentsOf: [ Envelope( topic: .userIntro(peerAddress), @@ -85,7 +85,7 @@ public struct ConversationV1 { ), ]) - client.contacts.hasIntroduced[peerAddress] = true + await client.contacts.markIntroduced(peerAddress, true) } return PreparedMessage(envelopes: envelopes) diff --git a/Sources/XMTP/Conversations.swift b/Sources/XMTP/Conversations.swift index eb5e9c4b..109e67ca 100644 --- a/Sources/XMTP/Conversations.swift +++ b/Sources/XMTP/Conversations.swift @@ -5,7 +5,7 @@ public enum ConversationError: Error { } /// Handles listing and creating Conversations. -public class Conversations { +public actor Conversations { var client: Client var conversationsByTopic: [String: Conversation] = [:] diff --git a/Tests/XMTPTests/ContactsTests.swift b/Tests/XMTPTests/ContactsTests.swift index 2d7b889d..072bb415 100644 --- a/Tests/XMTPTests/ContactsTests.swift +++ b/Tests/XMTPTests/ContactsTests.swift @@ -50,6 +50,7 @@ class ContactsTests: XCTestCase { XCTAssertEqual(contactBundle.walletAddress, fixtures.bob.walletAddress) } - XCTAssert(fixtures.aliceClient.contacts.has(fixtures.bob.walletAddress)) + let hasContact = await fixtures.aliceClient.contacts.has(fixtures.bob.walletAddress) + XCTAssert(hasContact) } } diff --git a/Tests/XMTPTests/ConversationTests.swift b/Tests/XMTPTests/ConversationTests.swift index a8abcd67..b1d824cd 100644 --- a/Tests/XMTPTests/ConversationTests.swift +++ b/Tests/XMTPTests/ConversationTests.swift @@ -106,7 +106,7 @@ class ConversationTests: XCTestCase { expectation1.expectedFulfillmentCount = 2 Task(priority: .userInitiated) { - for try await conversation in bobClient.conversations.stream() { + for try await conversation in await bobClient.conversations.stream() { expectation1.fulfill() } } @@ -549,30 +549,6 @@ class ConversationTests: XCTestCase { XCTAssertEqual("hi", decodedMessage2.body) } - func testCanSendEncodedContentV1Message() async throws { - try await publishLegacyContact(client: bobClient) - try await publishLegacyContact(client: aliceClient) - - guard case let .v1(bobConversation) = try await bobClient.conversations.newConversation(with: alice.address) else { - XCTFail("did not get a v1 conversation for alice") - return - } - - guard case let .v1(aliceConversation) = try await aliceClient.conversations.newConversation(with: bob.address) else { - XCTFail("did not get a v1 conversation for alice") - return - } - - let encodedContent = try TextCodec().encode(content: "hi") - - try await bobConversation.send(encodedContent: encodedContent, options: nil) - - let messages = try await aliceConversation.messages() - - XCTAssertEqual(1, messages.count) - XCTAssertEqual("hi", try messages[0].content()) - } - func testCanSendEncodedContentV2Message() async throws { guard case let .v2(bobConversation) = try await bobClient.conversations.newConversation(with: alice.address, context: InvitationV1.Context(conversationID: "hi")) else { XCTFail("did not get a v1 conversation for alice") @@ -594,50 +570,6 @@ class ConversationTests: XCTestCase { XCTAssertEqual("hi", try messages[0].content()) } - func testCanSendGzipCompressedV1Messages() async throws { - try await publishLegacyContact(client: bobClient) - try await publishLegacyContact(client: aliceClient) - - guard case let .v1(bobConversation) = try await bobClient.conversations.newConversation(with: alice.address) else { - XCTFail("did not get a v1 conversation for alice") - return - } - - guard case let .v1(aliceConversation) = try await aliceClient.conversations.newConversation(with: bob.address) else { - XCTFail("did not get a v1 conversation for alice") - return - } - - try await bobConversation.send(content: Array(repeating: "A", count: 1000).joined(), options: .init(compression: .gzip)) - - let messages = try await aliceConversation.messages() - - XCTAssertEqual(1, messages.count) - XCTAssertEqual(Array(repeating: "A", count: 1000).joined(), try messages[0].content()) - } - - func testCanSendDeflateCompressedV1Messages() async throws { - try await publishLegacyContact(client: bobClient) - try await publishLegacyContact(client: aliceClient) - - guard case let .v1(bobConversation) = try await bobClient.conversations.newConversation(with: alice.address) else { - XCTFail("did not get a v1 conversation for alice") - return - } - - guard case let .v1(aliceConversation) = try await aliceClient.conversations.newConversation(with: bob.address) else { - XCTFail("did not get a v1 conversation for alice") - return - } - - try await bobConversation.send(content: Array(repeating: "A", count: 1000).joined(), options: .init(compression: .deflate)) - - let messages = try await aliceConversation.messages() - - XCTAssertEqual(1, messages.count) - XCTAssertEqual(Array(repeating: "A", count: 1000).joined(), try messages[0].content()) - } - func testCanSendGzipCompressedV2Messages() async throws { guard case let .v2(bobConversation) = try await bobClient.conversations.newConversation(with: alice.address, context: InvitationV1.Context(conversationID: "hi")) else { XCTFail("did not get a v2 conversation for alice") diff --git a/Tests/XMTPTests/ConversationsTest.swift b/Tests/XMTPTests/ConversationsTest.swift index e4cd2285..76396403 100644 --- a/Tests/XMTPTests/ConversationsTest.swift +++ b/Tests/XMTPTests/ConversationsTest.swift @@ -29,7 +29,7 @@ class ConversationsTests: XCTestCase { let envelope = Envelope(topic: .userIntro(client.address), timestamp: created, message: try Message(v1: message).serializedData()) - let conversation = try client.conversations.fromIntro(envelope: envelope) + let conversation = try await client.conversations.fromIntro(envelope: envelope) XCTAssertEqual(conversation.peerAddress, newWallet.address) XCTAssertEqual(conversation.createdAt.description, created.description) } @@ -55,7 +55,7 @@ class ConversationsTests: XCTestCase { let peerAddress = fixtures.alice.walletAddress let envelope = Envelope(topic: .userInvite(peerAddress), timestamp: created, message: try sealed.serializedData()) - let conversation = try client.conversations.fromInvite(envelope: envelope) + let conversation = try await client.conversations.fromInvite(envelope: envelope) XCTAssertEqual(conversation.peerAddress, newWallet.address) XCTAssertEqual(conversation.createdAt.description, created.description) } diff --git a/Tests/XMTPTests/IntegrationTests.swift b/Tests/XMTPTests/IntegrationTests.swift index be93d24f..abaae304 100644 --- a/Tests/XMTPTests/IntegrationTests.swift +++ b/Tests/XMTPTests/IntegrationTests.swift @@ -162,7 +162,7 @@ final class IntegrationTests: XCTestCase { options: opt ) // And it uses the saved topic data for the conversation - let aliceConvo2 = alice2.conversations.importTopicData( + let aliceConvo2 = await alice2.conversations.importTopicData( data: try Xmtp_KeystoreApi_V1_TopicMap.TopicData(serializedData: topicData)) XCTAssertEqual("example.com/alice-bob-1", aliceConvo2.conversationID) @@ -520,7 +520,7 @@ final class IntegrationTests: XCTestCase { expectation1.expectedFulfillmentCount = 2 Task(priority: .userInitiated) { - for try await convo in bobClient.conversations.stream() { + for try await convo in await bobClient.conversations.stream() { expectation1.fulfill() } } diff --git a/Tests/XMTPTests/PaginationTests.swift b/Tests/XMTPTests/PaginationTests.swift index a1534edf..8b792ab9 100644 --- a/Tests/XMTPTests/PaginationTests.swift +++ b/Tests/XMTPTests/PaginationTests.swift @@ -85,7 +85,7 @@ class PaginationTests: XCTestCase { expectation1.expectedFulfillmentCount = 2 Task(priority: .userInitiated) { - for try await _ in bobClient.conversations.stream() { + for try await _ in await bobClient.conversations.stream() { print("Got one conversation") expectation1.fulfill() } diff --git a/XMTP.podspec b/XMTP.podspec index da7a1088..75c969ae 100644 --- a/XMTP.podspec +++ b/XMTP.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |spec| # spec.name = "XMTP" - spec.version = "0.5.7-alpha0" + spec.version = "0.5.8-alpha0" spec.summary = "XMTP SDK Cocoapod" # This description is used to generate tags and improve search results.