From ede81225aec813acb1061c76ffbb2fcc6b07fea3 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 13 Sep 2024 13:22:24 -0600 Subject: [PATCH 01/13] 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 3931d15aecadb56095af04c2538592a667ae1934 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 19 Sep 2024 23:37:35 -0600 Subject: [PATCH 02/13] setup all the ground work for a v3 only client --- Sources/XMTPiOS/Client.swift | 112 ++++++++++++++---- Tests/XMTPTests/ClientTests.swift | 19 +++ Tests/XMTPTests/V3Client.swift | 68 +++++++++++ XMTP.podspec | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- 5 files changed, 181 insertions(+), 24 deletions(-) create mode 100644 Tests/XMTPTests/V3Client.swift diff --git a/Sources/XMTPiOS/Client.swift b/Sources/XMTPiOS/Client.swift index 62071acf..ec4de35a 100644 --- a/Sources/XMTPiOS/Client.swift +++ b/Sources/XMTPiOS/Client.swift @@ -14,6 +14,7 @@ public typealias PreEventCallback = () async throws -> Void public enum ClientError: Error, CustomStringConvertible, LocalizedError { case creationError(String) case noV3Client(String) + case noV2Client(String) public var description: String { switch self { @@ -21,6 +22,8 @@ public enum ClientError: Error, CustomStringConvertible, LocalizedError { return "ClientError.creationError: \(err)" case .noV3Client(let err): return "ClientError.noV3Client: \(err)" + case .noV2Client(let err): + return "ClientError.noV2Client: \(err)" } } @@ -111,13 +114,15 @@ public struct ClientOptions { public final class Client { /// The wallet address of the ``SigningKey`` used to create this Client. public let address: String - let privateKeyBundleV1: PrivateKeyBundleV1 - let apiClient: ApiClient - let v3Client: LibXMTP.FfiXmtpClient? + let privateKeyBundleV1: PrivateKeyBundleV1? + let apiClient: ApiClient? + public let v3Client: LibXMTP.FfiXmtpClient? public let libXMTPVersion: String = getVersionInfo() public let dbPath: String public let installationID: String public let inboxID: String + public var hasV2Client: Bool = true + /// Access ``Conversations`` for this Client. public lazy var conversations: Conversations = .init(client: self) @@ -126,9 +131,7 @@ public final class Client { public lazy var contacts: Contacts = .init(client: self) /// The XMTP environment which specifies which network this Client is connected to. - public var environment: XMTPEnvironment { - apiClient.environment - } + public lazy var environment: XMTPEnvironment = apiClient?.environment ?? .dev var codecRegistry = CodecRegistry() @@ -157,11 +160,43 @@ public final class Client { throw ClientError.creationError(detailedErrorMessage) } } + + // This is a V3 only feature + public static func createOrBuild(account: SigningKey, options: ClientOptions) async throws -> Client { + let inboxId = try await getOrCreateInboxId(options: options, address: account.address) + + let (libxmtpClient, dbPath) = try await initV3Client( + accountAddress: account.address, + options: options, + privateKeyBundleV1: nil, + signingKey: account, + inboxId: inboxId + ) + guard let v3Client = libxmtpClient else { + throw ClientError.noV3Client("Error no V3 client initialized") + } + + let client = try Client( + address: account.address, + v3Client: v3Client, + dbPath: dbPath, + installationID: v3Client.installationId().toHex, + inboxID: v3Client.inboxId(), + environment: options.api.env + ) + + let conversations = client.conversations + let contacts = client.contacts + + for codec in (options.codecs) { + client.register(codec: codec) + } + } static func initV3Client( accountAddress: String, options: ClientOptions?, - privateKeyBundleV1: PrivateKeyBundleV1, + privateKeyBundleV1: PrivateKeyBundleV1?, signingKey: SigningKey?, inboxId: String ) async throws -> (FfiXmtpClient?, String) { @@ -202,7 +237,7 @@ public final class Client { inboxId: inboxId, accountAddress: address, nonce: 0, - legacySignedPrivateKeyProto: try privateKeyBundleV1.toV2().identityKey.serializedData(), + legacySignedPrivateKeyProto: try privateKeyBundleV1?.toV2().identityKey.serializedData(), historySyncUrl: options?.historySyncUrl ) @@ -377,22 +412,45 @@ public final class Client { self.dbPath = dbPath self.installationID = installationID self.inboxID = inboxID + self.hasV2Client = true + self.environment = apiClient.environment + } + + init(address: String, v3Client: LibXMTP.FfiXmtpClient, dbPath: String, installationID: String, inboxID: String, environment: XMTPEnvironment) throws { + self.address = address + self.v3Client = v3Client + self.dbPath = dbPath + self.installationID = installationID + self.inboxID = inboxID + self.hasV2Client = false + self.environment = environment } public var privateKeyBundle: PrivateKeyBundle { - PrivateKeyBundle(v1: privateKeyBundleV1) + get throws { + try PrivateKeyBundle(v1: v1keys) + } } public var publicKeyBundle: SignedPublicKeyBundle { - privateKeyBundleV1.toV2().getPublicKeyBundle() + get throws { + try v1keys.toV2().getPublicKeyBundle() + } } public var v1keys: PrivateKeyBundleV1 { - privateKeyBundleV1 + get throws { + guard let keys = privateKeyBundleV1 else { + throw ClientError.noV2Client("Error no V2 client initialized") + } + return keys + } } public var keys: PrivateKeyBundleV2 { - privateKeyBundleV1.toV2() + get throws { + try v1keys.toV2() + } } public func canMessage(_ peerAddress: String) async throws -> Bool { @@ -472,7 +530,7 @@ public final class Client { func ensureUserContactPublished() async throws { if let contact = try await getUserContact(peerAddress: address), case .v2 = contact.version, - keys.getPublicKeyBundle().equals(contact.v2.keyBundle) + try keys.getPublicKeyBundle().equals(contact.v2.keyBundle) { return } @@ -485,7 +543,7 @@ public final class Client { if legacy { var contactBundle = ContactBundle() - contactBundle.v1.keyBundle = privateKeyBundleV1.toPublicKeyBundle() + contactBundle.v1.keyBundle = try v1keys.toPublicKeyBundle() var envelope = Envelope() envelope.contentTopic = Topic.contact(address).description @@ -496,7 +554,7 @@ public final class Client { } var contactBundle = ContactBundle() - contactBundle.v2.keyBundle = keys.getPublicKeyBundle() + contactBundle.v2.keyBundle = try keys.getPublicKeyBundle() contactBundle.v2.keyBundle.identityKey.signature.ensureWalletSignature() var envelope = Envelope() @@ -509,23 +567,32 @@ public final class Client { } public func query(topic: Topic, pagination: Pagination? = nil) async throws -> QueryResponse { - return try await apiClient.query( + guard let client = apiClient else { + throw ClientError.noV2Client("Error no V2 client initialized") + } + return try await client.query( topic: topic, pagination: pagination ) } public func batchQuery(request: BatchQueryRequest) async throws -> BatchQueryResponse { - return try await apiClient.batchQuery(request: request) + guard let client = apiClient else { + throw ClientError.noV2Client("Error no V2 client initialized") + } + return try await client.batchQuery(request: request) } public func publish(envelopes: [Envelope]) async throws { - let authorized = AuthorizedIdentity(address: address, authorized: privateKeyBundleV1.identityKey.publicKey, identity: privateKeyBundleV1.identityKey) + guard let client = apiClient else { + throw ClientError.noV2Client("Error no V2 client initialized") + } + let authorized = try AuthorizedIdentity(address: address, authorized: v1keys.identityKey.publicKey, identity: v1keys.identityKey) let authToken = try await authorized.createAuthToken() - apiClient.setAuthToken(authToken) + client.setAuthToken(authToken) - try await apiClient.publish(envelopes: envelopes) + try await client.publish(envelopes: envelopes) } public func subscribe( @@ -539,7 +606,10 @@ public final class Client { request: FfiV2SubscribeRequest, callback: FfiV2SubscriptionCallback ) async throws -> FfiV2Subscription { - return try await apiClient.subscribe(request: request, callback: callback) + guard let client = apiClient else { + throw ClientError.noV2Client("Error no V2 client initialized") + } + return try await client.subscribe(request: request, callback: callback) } public func deleteLocalDatabase() throws { diff --git a/Tests/XMTPTests/ClientTests.swift b/Tests/XMTPTests/ClientTests.swift index 27c0381a..362da66d 100644 --- a/Tests/XMTPTests/ClientTests.swift +++ b/Tests/XMTPTests/ClientTests.swift @@ -456,6 +456,25 @@ class ClientTests: XCTestCase { XCTAssertEqual(inboxId, alixClient.inboxID) } + func testCreatesAPureV3Client() async throws { + let key = try Crypto.secureRandomBytes(count: 32) + let alix = try PrivateKey.generate() + let options = ClientOptions.init( + api: .init(env: .local, isSecure: false), + enableV3: true, + encryptionKey: 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 testRevokesAllOtherInstallations() async throws { let key = try Crypto.secureRandomBytes(count: 32) let alix = try PrivateKey.generate() diff --git a/Tests/XMTPTests/V3Client.swift b/Tests/XMTPTests/V3Client.swift new file mode 100644 index 00000000..f0dadb4a --- /dev/null +++ b/Tests/XMTPTests/V3Client.swift @@ -0,0 +1,68 @@ +// +// File.swift +// +// +// Created by Naomi Plasterer on 9/19/24. +// + +import CryptoKit +import XCTest +@testable import XMTPiOS +import LibXMTP +import XMTPTestHelpers + +@available(iOS 16, *) +class V3ClientTests: XCTestCase { + // Use these fixtures to talk to the local node + struct LocalFixtures { + var alice: PrivateKey! + var bob: PrivateKey! + var fred: PrivateKey! + var aliceClient: Client! + var bobClient: Client! + var fredClient: Client! + } + + func localFixtures() async throws -> LocalFixtures { + let key = try Crypto.secureRandomBytes(count: 32) + let alice = try PrivateKey.generate() + let aliceClient = try await Client.create( + account: alice, + options: .init( + api: .init(env: .local, isSecure: false), + codecs: [GroupUpdatedCodec()], + enableV3: true, + encryptionKey: key + ) + ) + let bob = try PrivateKey.generate() + let bobClient = try await Client.create( + account: bob, + options: .init( + api: .init(env: .local, isSecure: false), + codecs: [GroupUpdatedCodec()], + enableV3: true, + encryptionKey: key + ) + ) + let fred = try PrivateKey.generate() + let fredClient = try await Client.create( + account: fred, + options: .init( + api: .init(env: .local, isSecure: false), + codecs: [GroupUpdatedCodec()], + enableV3: true, + encryptionKey: key + ) + ) + + return .init( + alice: alice, + bob: bob, + fred: fred, + aliceClient: aliceClient, + bobClient: bobClient, + fredClient: fredClient + ) + } +} diff --git a/XMTP.podspec b/XMTP.podspec index b9741002..efe9d3b0 100644 --- a/XMTP.podspec +++ b/XMTP.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |spec| # spec.name = "XMTP" - spec.version = "0.14.13" + spec.version = "0.14.14" spec.summary = "XMTP SDK Cocoapod" # This description is used to generate tags and improve search results. diff --git a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4d3d866a..73812576 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" : "abd4f896f539e5bb090c85022177d775ad08dcb1", - "version" : "0.5.8-beta4" + "revision" : "9d5153926ac1bfcab76802d5a7626c2cf47212a4", + "version" : "0.5.8-beta5" } }, { From e0d509179de8b59f982213eeed228c9a61473f5c Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 19 Sep 2024 23:52:01 -0600 Subject: [PATCH 03/13] get the first test passing --- Sources/XMTPTestHelpers/TestHelpers.swift | 2 +- Sources/XMTPiOS/Client.swift | 5 +- Sources/XMTPiOS/Contacts.swift | 193 +++++++++++----------- Sources/XMTPiOS/ConversationV1.swift | 15 +- Sources/XMTPiOS/ConversationV2.swift | 12 +- Sources/XMTPiOS/Conversations.swift | 22 ++- Sources/XMTPiOS/Frames/FramesClient.swift | 4 +- Sources/XMTPiOS/Messages/MessageV2.swift | 4 +- Tests/XMTPTests/ClientTests.swift | 28 ++-- Tests/XMTPTests/ConversationTests.swift | 8 +- Tests/XMTPTests/ConversationsTest.swift | 2 +- Tests/XMTPTests/IntegrationTests.swift | 8 +- 12 files changed, 166 insertions(+), 137 deletions(-) diff --git a/Sources/XMTPTestHelpers/TestHelpers.swift b/Sources/XMTPTestHelpers/TestHelpers.swift index e6d9a940..d9a43f26 100644 --- a/Sources/XMTPTestHelpers/TestHelpers.swift +++ b/Sources/XMTPTestHelpers/TestHelpers.swift @@ -88,7 +88,7 @@ public struct Fixtures { public func publishLegacyContact(client: Client) async throws { var contactBundle = ContactBundle() - contactBundle.v1.keyBundle = client.privateKeyBundleV1.toPublicKeyBundle() + contactBundle.v1.keyBundle = try client.v1keys.toPublicKeyBundle() var envelope = Envelope() envelope.contentTopic = Topic.contact(client.address).description diff --git a/Sources/XMTPiOS/Client.swift b/Sources/XMTPiOS/Client.swift index ec4de35a..e0da501d 100644 --- a/Sources/XMTPiOS/Client.swift +++ b/Sources/XMTPiOS/Client.swift @@ -114,8 +114,8 @@ public struct ClientOptions { public final class Client { /// The wallet address of the ``SigningKey`` used to create this Client. public let address: String - let privateKeyBundleV1: PrivateKeyBundleV1? - let apiClient: ApiClient? + var privateKeyBundleV1: PrivateKeyBundleV1? = nil + var apiClient: ApiClient? = nil public let v3Client: LibXMTP.FfiXmtpClient? public let libXMTPVersion: String = getVersionInfo() public let dbPath: String @@ -191,6 +191,7 @@ public final class Client { for codec in (options.codecs) { client.register(codec: codec) } + return client } static func initV3Client( diff --git a/Sources/XMTPiOS/Contacts.swift b/Sources/XMTPiOS/Contacts.swift index 3f1d91f3..23d55358 100644 --- a/Sources/XMTPiOS/Contacts.swift +++ b/Sources/XMTPiOS/Contacts.swift @@ -58,63 +58,63 @@ public actor EntriesManager { public class ConsentList { public let entriesManager = EntriesManager() - var publicKey: Data - var privateKey: Data - var identifier: String? var lastFetched: Date? var client: Client init(client: Client) { self.client = client - privateKey = client.privateKeyBundleV1.identityKey.secp256K1.bytes - publicKey = client.privateKeyBundleV1.identityKey.publicKey.secp256K1Uncompressed.bytes - identifier = try? LibXMTP.generatePrivatePreferencesTopicIdentifier(privateKey: privateKey) } func load() async throws -> [ConsentListEntry] { - guard let identifier = identifier else { - throw ContactError.invalidIdentifier - } - let newDate = Date() - - let pagination = Pagination( - limit: 500, - after: lastFetched, - direction: .ascending - ) - let envelopes = try await client.apiClient.envelopes(topic: Topic.preferenceList(identifier).description, pagination: pagination) - lastFetched = newDate - - var preferences: [PrivatePreferencesAction] = [] - - for envelope in envelopes { - let payload = try LibXMTP.userPreferencesDecrypt(publicKey: publicKey, privateKey: privateKey, message: envelope.message) - - try preferences.append(PrivatePreferencesAction(serializedData: Data(payload))) - } - for preference in preferences { - for address in preference.allowAddress.walletAddresses { - _ = await allow(address: address) - } - - for address in preference.denyAddress.walletAddresses { - _ = await deny(address: address) - } - - for groupId in preference.allowGroup.groupIds { - _ = await allowGroup(groupId: groupId) - } - - for groupId in preference.denyGroup.groupIds { - _ = await denyGroup(groupId: groupId) + if (client.hasV2Client) { + let privateKey = try client.v1keys.identityKey.secp256K1.bytes + let publicKey = try client.v1keys.identityKey.publicKey.secp256K1Uncompressed.bytes + let identifier = try? LibXMTP.generatePrivatePreferencesTopicIdentifier(privateKey: privateKey) + + guard let identifier = identifier else { + throw ContactError.invalidIdentifier } + let newDate = Date() + + let pagination = Pagination( + limit: 500, + after: lastFetched, + direction: .ascending + ) + let envelopes = try await client.apiClient!.envelopes(topic: Topic.preferenceList(identifier).description, pagination: pagination) + lastFetched = newDate - for inboxId in preference.allowInboxID.inboxIds { - _ = await allowInboxId(inboxId: inboxId) + var preferences: [PrivatePreferencesAction] = [] + + for envelope in envelopes { + let payload = try LibXMTP.userPreferencesDecrypt(publicKey: publicKey, privateKey: privateKey, message: envelope.message) + + try preferences.append(PrivatePreferencesAction(serializedData: Data(payload))) } - - for inboxId in preference.denyInboxID.inboxIds { - _ = await denyInboxId(inboxId: inboxId) + for preference in preferences { + for address in preference.allowAddress.walletAddresses { + _ = await allow(address: address) + } + + for address in preference.denyAddress.walletAddresses { + _ = await deny(address: address) + } + + for groupId in preference.allowGroup.groupIds { + _ = await allowGroup(groupId: groupId) + } + + for groupId in preference.denyGroup.groupIds { + _ = await denyGroup(groupId: groupId) + } + + for inboxId in preference.allowInboxID.inboxIds { + _ = await allowInboxId(inboxId: inboxId) + } + + for inboxId in preference.denyInboxID.inboxIds { + _ = await denyInboxId(inboxId: inboxId) + } } } @@ -122,56 +122,61 @@ public class ConsentList { } func publish(entries: [ConsentListEntry]) async throws { - guard let identifier = identifier else { - throw ContactError.invalidIdentifier - } - var payload = PrivatePreferencesAction() - - for entry in entries { - switch entry.entryType { - case .address: - switch entry.consentType { - case .allowed: - payload.allowAddress.walletAddresses.append(entry.value) - case .denied: - payload.denyAddress.walletAddresses.append(entry.value) - case .unknown: - payload.messageType = nil - } - case .group_id: - switch entry.consentType { - case .allowed: - payload.allowGroup.groupIds.append(entry.value) - case .denied: - payload.denyGroup.groupIds.append(entry.value) - case .unknown: - payload.messageType = nil - } - case .inbox_id: - switch entry.consentType { - case .allowed: - payload.allowInboxID.inboxIds.append(entry.value) - case .denied: - payload.denyInboxID.inboxIds.append(entry.value) - case .unknown: - payload.messageType = nil + if (client.hasV2Client) { + let privateKey = try client.v1keys.identityKey.secp256K1.bytes + let publicKey = try client.v1keys.identityKey.publicKey.secp256K1Uncompressed.bytes + let identifier = try? LibXMTP.generatePrivatePreferencesTopicIdentifier(privateKey: privateKey) + guard let identifier = identifier else { + throw ContactError.invalidIdentifier } + var payload = PrivatePreferencesAction() + + for entry in entries { + switch entry.entryType { + case .address: + switch entry.consentType { + case .allowed: + payload.allowAddress.walletAddresses.append(entry.value) + case .denied: + payload.denyAddress.walletAddresses.append(entry.value) + case .unknown: + payload.messageType = nil + } + case .group_id: + switch entry.consentType { + case .allowed: + payload.allowGroup.groupIds.append(entry.value) + case .denied: + payload.denyGroup.groupIds.append(entry.value) + case .unknown: + payload.messageType = nil + } + case .inbox_id: + switch entry.consentType { + case .allowed: + payload.allowInboxID.inboxIds.append(entry.value) + case .denied: + payload.denyInboxID.inboxIds.append(entry.value) + case .unknown: + payload.messageType = nil + } + } + } + + let message = try LibXMTP.userPreferencesEncrypt( + publicKey: publicKey, + privateKey: privateKey, + message: payload.serializedData() + ) + + let envelope = Envelope( + topic: Topic.preferenceList(identifier), + timestamp: Date(), + message: Data(message) + ) + + try await client.publish(envelopes: [envelope]) } - } - - let message = try LibXMTP.userPreferencesEncrypt( - publicKey: publicKey, - privateKey: privateKey, - message: payload.serializedData() - ) - - let envelope = Envelope( - topic: Topic.preferenceList(identifier), - timestamp: Date(), - message: Data(message) - ) - - try await client.publish(envelopes: [envelope]) } func allow(address: String) async -> ConsentListEntry { diff --git a/Sources/XMTPiOS/ConversationV1.swift b/Sources/XMTPiOS/ConversationV1.swift index 2b187ded..8e62f037 100644 --- a/Sources/XMTPiOS/ConversationV1.swift +++ b/Sources/XMTPiOS/ConversationV1.swift @@ -52,7 +52,7 @@ public struct ConversationV1 { let date = sentAt let message = try MessageV1.encode( - sender: client.privateKeyBundleV1, + sender: client.v1keys, recipient: recipient, message: try encodedContent.serializedData(), timestamp: date @@ -217,8 +217,10 @@ public struct ConversationV1 { func decryptedMessages(limit: Int? = nil, before: Date? = nil, after: Date? = nil, direction: PagingInfoSortDirection? = .descending) async throws -> [DecryptedMessage] { let pagination = Pagination(limit: limit, before: before, after: after, direction: direction) - - let envelopes = try await client.apiClient.envelopes( + guard let apiClient = client.apiClient else { + throw ClientError.noV2Client("Error no V2 client initialized") + } + let envelopes = try await apiClient.envelopes( topic: Topic.directMessageV1(client.address, peerAddress).description, pagination: pagination ) @@ -229,7 +231,10 @@ public struct ConversationV1 { func messages(limit: Int? = nil, before: Date? = nil, after: Date? = nil, direction: PagingInfoSortDirection? = .descending) async throws -> [DecodedMessage] { let pagination = Pagination(limit: limit, before: before, after: after, direction: direction) - let envelopes = try await client.apiClient.envelopes( + guard let apiClient = client.apiClient else { + throw ClientError.noV2Client("Error no V2 client initialized") + } + let envelopes = try await apiClient.envelopes( topic: Topic.directMessageV1(client.address, peerAddress).description, pagination: pagination ) @@ -246,7 +251,7 @@ public struct ConversationV1 { func decrypt(envelope: Envelope) throws -> DecryptedMessage { let message = try Message(serializedData: envelope.message) - let decrypted = try message.v1.decrypt(with: client.privateKeyBundleV1) + let decrypted = try message.v1.decrypt(with: client.v1keys) let encodedMessage = try EncodedContent(serializedData: decrypted) let header = try message.v1.header diff --git a/Sources/XMTPiOS/ConversationV2.swift b/Sources/XMTPiOS/ConversationV2.swift index 86c81974..cb479b2e 100644 --- a/Sources/XMTPiOS/ConversationV2.swift +++ b/Sources/XMTPiOS/ConversationV2.swift @@ -37,7 +37,7 @@ public struct ConversationV2 { private var header: SealedInvitationHeaderV1 static func create(client: Client, invitation: InvitationV1, header: SealedInvitationHeaderV1) throws -> ConversationV2 { - let myKeys = client.keys.getPublicKeyBundle() + let myKeys = try client.keys.getPublicKeyBundle() let peer = try myKeys.walletAddress == (try header.sender.walletAddress) ? header.recipient : header.sender let peerAddress = try peer.walletAddress @@ -133,7 +133,10 @@ public struct ConversationV2 { func messages(limit: Int? = nil, before: Date? = nil, after: Date? = nil, direction: PagingInfoSortDirection? = .descending) async throws -> [DecodedMessage] { let pagination = Pagination(limit: limit, before: before, after: after, direction: direction) - let envelopes = try await client.apiClient.envelopes(topic: topic.description, pagination: pagination) + guard let apiClient = client.apiClient else { + throw ClientError.noV2Client("Error no V2 client initialized") + } + let envelopes = try await apiClient.envelopes(topic: topic.description, pagination: pagination) return envelopes.compactMap { envelope in do { @@ -146,8 +149,11 @@ public struct ConversationV2 { } func decryptedMessages(limit: Int? = nil, before: Date? = nil, after: Date? = nil, direction: PagingInfoSortDirection? = .descending) async throws -> [DecryptedMessage] { + guard let apiClient = client.apiClient else { + throw ClientError.noV2Client("Error no V2 client initialized") + } let pagination = Pagination(limit: limit, before: before, after: after, direction: direction) - let envelopes = try await client.apiClient.envelopes(topic: topic.description, pagination: pagination) + let envelopes = try await apiClient.envelopes(topic: topic.description, pagination: pagination) return try envelopes.map { envelope in try decrypt(envelope: envelope) diff --git a/Sources/XMTPiOS/Conversations.swift b/Sources/XMTPiOS/Conversations.swift index f35637c7..04c70cee 100644 --- a/Sources/XMTPiOS/Conversations.swift +++ b/Sources/XMTPiOS/Conversations.swift @@ -296,8 +296,11 @@ public actor Conversations { .map { requests in BatchQueryRequest.with { $0.requests = requests } } var messages: [DecodedMessage] = [] // TODO: consider using a task group here for parallel batch calls + guard let apiClient = client.apiClient else { + throw ClientError.noV2Client("Error no V2 client initialized") + } for batch in batches { - messages += try await client.apiClient.batchQuery(request: batch) + messages += try await apiClient.batchQuery(request: batch) .responses.flatMap { res in res.envelopes.compactMap { envelope in let conversation = conversationsByTopic[envelope.contentTopic] @@ -327,8 +330,11 @@ public actor Conversations { .map { requests in BatchQueryRequest.with { $0.requests = requests } } var messages: [DecryptedMessage] = [] // TODO: consider using a task group here for parallel batch calls + guard let apiClient = client.apiClient else { + throw ClientError.noV2Client("Error no V2 client initialized") + } for batch in batches { - messages += try await client.apiClient.batchQuery(request: batch) + messages += try await apiClient.batchQuery(request: batch) .responses.flatMap { res in res.envelopes.compactMap { envelope in let conversation = conversationsByTopic[envelope.contentTopic] @@ -833,7 +839,10 @@ public actor Conversations { } private func listIntroductionPeers(pagination: Pagination?) async throws -> [String: Date] { - let envelopes = try await client.apiClient.query( + guard let apiClient = client.apiClient else { + throw ClientError.noV2Client("Error no V2 client initialized") + } + let envelopes = try await apiClient.query( topic: .userIntro(client.address), pagination: pagination ).envelopes @@ -841,7 +850,7 @@ public actor Conversations { do { let message = try MessageV1.fromBytes(envelope.message) // Attempt to decrypt, just to make sure we can - _ = try message.decrypt(with: client.privateKeyBundleV1) + _ = try message.decrypt(with: client.v1keys) return message } catch { return nil @@ -867,7 +876,10 @@ public actor Conversations { } private func listInvitations(pagination: Pagination?) async throws -> [SealedInvitation] { - var envelopes = try await client.apiClient.envelopes( + guard let apiClient = client.apiClient else { + throw ClientError.noV2Client("Error no V2 client initialized") + } + var envelopes = try await apiClient.envelopes( topic: Topic.userInvite(client.address).description, pagination: pagination ) diff --git a/Sources/XMTPiOS/Frames/FramesClient.swift b/Sources/XMTPiOS/Frames/FramesClient.swift index e234e53b..0fea2726 100644 --- a/Sources/XMTPiOS/Frames/FramesClient.swift +++ b/Sources/XMTPiOS/Frames/FramesClient.swift @@ -62,14 +62,14 @@ public class FramesClient { } private func signDigest(digest: Data) async throws -> Signature { - let key = self.xmtpClient.keys.identityKey + let key = try self.xmtpClient.keys.identityKey let privateKey = try PrivateKey(key) let signature = try await privateKey.sign(Data(digest)) return signature } private func getPublicKeyBundle() async throws -> PublicKeyBundle { - let bundleBytes = self.xmtpClient.publicKeyBundle; + let bundleBytes = try self.xmtpClient.publicKeyBundle; return try PublicKeyBundle(bundleBytes); } diff --git a/Sources/XMTPiOS/Messages/MessageV2.swift b/Sources/XMTPiOS/Messages/MessageV2.swift index 66e1816f..3e5d0090 100644 --- a/Sources/XMTPiOS/Messages/MessageV2.swift +++ b/Sources/XMTPiOS/Messages/MessageV2.swift @@ -88,10 +88,10 @@ extension MessageV2 { let headerBytes = try header.serializedData() let digest = SHA256.hash(data: headerBytes + payload) - let preKey = client.keys.preKeys[0] + let preKey = try client.keys.preKeys[0] let signature = try await preKey.sign(Data(digest)) - let bundle = client.privateKeyBundleV1.toV2().getPublicKeyBundle() + let bundle = try client.v1keys.toV2().getPublicKeyBundle() let signedContent = SignedContent(payload: payload, sender: bundle, signature: signature) let signedBytes = try signedContent.serializedData() diff --git a/Tests/XMTPTests/ClientTests.swift b/Tests/XMTPTests/ClientTests.swift index 362da66d..43599ffe 100644 --- a/Tests/XMTPTests/ClientTests.swift +++ b/Tests/XMTPTests/ClientTests.swift @@ -50,7 +50,7 @@ class ClientTests: XCTestCase { ) ) - let keys = client.privateKeyBundle + let keys = try client.privateKeyBundle let otherClient = try await Client.from( bundle: keys, options: .init( @@ -204,9 +204,9 @@ class ClientTests: XCTestCase { let fakeWallet = try PrivateKey.generate() let client = try await Client.create(account: fakeWallet, options: opts) - XCTAssertEqual(1, client.privateKeyBundleV1.preKeys.count) + XCTAssertEqual(1, try client.v1keys.preKeys.count) - let preKey = client.privateKeyBundleV1.preKeys[0] + let preKey = try client.v1keys.preKeys[0] XCTAssert(preKey.publicKey.hasSignature, "prekey not signed") } @@ -216,12 +216,12 @@ class ClientTests: XCTestCase { let fakeWallet = try PrivateKey.generate() let client = try await Client.create(account: fakeWallet) - let bundle = client.privateKeyBundle + let bundle = try client.privateKeyBundle let clientFromV1Bundle = try await Client.from(bundle: bundle) XCTAssertEqual(client.address, clientFromV1Bundle.address) - XCTAssertEqual(client.privateKeyBundleV1.identityKey, clientFromV1Bundle.privateKeyBundleV1.identityKey) - XCTAssertEqual(client.privateKeyBundleV1.preKeys, clientFromV1Bundle.privateKeyBundleV1.preKeys) + XCTAssertEqual(try client.v1keys.identityKey, try clientFromV1Bundle.v1keys.identityKey) + XCTAssertEqual(try client.v1keys.preKeys, try clientFromV1Bundle.v1keys.preKeys) } func testCanBeCreatedWithV1Bundle() async throws { @@ -229,20 +229,20 @@ class ClientTests: XCTestCase { let fakeWallet = try PrivateKey.generate() let client = try await Client.create(account: fakeWallet) - let bundleV1 = client.v1keys + let bundleV1 = try client.v1keys let clientFromV1Bundle = try await Client.from(v1Bundle: bundleV1) XCTAssertEqual(client.address, clientFromV1Bundle.address) - XCTAssertEqual(client.privateKeyBundleV1.identityKey, clientFromV1Bundle.privateKeyBundleV1.identityKey) - XCTAssertEqual(client.privateKeyBundleV1.preKeys, clientFromV1Bundle.privateKeyBundleV1.preKeys) + XCTAssertEqual(try client.v1keys.identityKey, try clientFromV1Bundle.v1keys.identityKey) + XCTAssertEqual(try client.v1keys.preKeys, try clientFromV1Bundle.v1keys.preKeys) } func testCanAccessPublicKeyBundle() async throws { let fakeWallet = try PrivateKey.generate() let client = try await Client.create(account: fakeWallet) - let publicKeyBundle = client.keys.getPublicKeyBundle() - XCTAssertEqual(publicKeyBundle, client.publicKeyBundle) + let publicKeyBundle = try client.keys.getPublicKeyBundle() + XCTAssertEqual(publicKeyBundle, try client.publicKeyBundle) } func testCanSignWithPrivateIdentityKey() async throws { @@ -254,7 +254,7 @@ class ClientTests: XCTestCase { let recovered = try KeyUtilx.recoverPublicKeyKeccak256(from: signature.rawData, message: Data("hello world".utf8)) - XCTAssertEqual(recovered, client.keys.identityKey.publicKey.secp256K1Uncompressed.bytes) + XCTAssertEqual(recovered, try client.keys.identityKey.publicKey.secp256K1Uncompressed.bytes) } func testPreEnableIdentityCallback() async throws { @@ -331,7 +331,7 @@ class ClientTests: XCTestCase { ) ) - let keys = client.privateKeyBundle + let keys = try client.privateKeyBundle let bundleClient = try await Client.from( bundle: keys, options: .init( @@ -467,7 +467,7 @@ class ClientTests: XCTestCase { let inboxId = try await Client.getOrCreateInboxId(options: options, address: alix.address) - let alixClient = try await Client.create( + let alixClient = try await Client.createOrBuild( account: alix, options: options ) diff --git a/Tests/XMTPTests/ConversationTests.swift b/Tests/XMTPTests/ConversationTests.swift index 04721db8..ea7ffb8e 100644 --- a/Tests/XMTPTests/ConversationTests.swift +++ b/Tests/XMTPTests/ConversationTests.swift @@ -145,7 +145,7 @@ class ConversationTests: XCTestCase { func publishLegacyContact(client: Client) async throws { var contactBundle = ContactBundle() - contactBundle.v1.keyBundle = client.privateKeyBundleV1.toPublicKeyBundle() + contactBundle.v1.keyBundle = try client.v1keys.toPublicKeyBundle() var envelope = Envelope() envelope.contentTopic = Topic.contact(client.address).description @@ -215,10 +215,10 @@ class ConversationTests: XCTestCase { let headerBytes = try header.serializedData() let digest = SHA256.hash(data: headerBytes + tamperedPayload) - let preKey = aliceClient.keys.preKeys[0] + let preKey = try aliceClient.keys.preKeys[0] let signature = try await preKey.sign(Data(digest)) - let bundle = aliceClient.privateKeyBundleV1.toV2().getPublicKeyBundle() + let bundle = try aliceClient.v1keys.toV2().getPublicKeyBundle() let signedContent = SignedContent(payload: originalPayload, sender: bundle, signature: signature) let signedBytes = try signedContent.serializedData() @@ -375,7 +375,7 @@ class ConversationTests: XCTestCase { try await bobConversation.send(content: "Hello") // Now we send some garbage and expect it to be properly ignored. - try await bobClient.apiClient.publish(envelopes: [ + try await bobClient.apiClient!.publish(envelopes: [ Envelope( topic: bobConversation.topic, timestamp: Date(), diff --git a/Tests/XMTPTests/ConversationsTest.swift b/Tests/XMTPTests/ConversationsTest.swift index a1c0823d..90325267 100644 --- a/Tests/XMTPTests/ConversationsTest.swift +++ b/Tests/XMTPTests/ConversationsTest.swift @@ -21,7 +21,7 @@ class ConversationsTests: XCTestCase { let created = Date() let message = try MessageV1.encode( - sender: fixtures.bobClient.privateKeyBundleV1, + sender: try fixtures.bobClient.v1keys, recipient: fixtures.aliceClient.v1keys.toPublicKeyBundle(), message: try TextCodec().encode(content: "hello", client: client).serializedData(), timestamp: created diff --git a/Tests/XMTPTests/IntegrationTests.swift b/Tests/XMTPTests/IntegrationTests.swift index 06a1231d..8cccdd17 100644 --- a/Tests/XMTPTests/IntegrationTests.swift +++ b/Tests/XMTPTests/IntegrationTests.swift @@ -52,7 +52,7 @@ final class IntegrationTests: XCTestCase { try await delayToPropagate() let contact = try await alice.getUserContact(peerAddress: alice.address) - XCTAssertEqual(contact!.v2.keyBundle.identityKey.secp256K1Uncompressed, alice.privateKeyBundleV1.identityKey.publicKey.secp256K1Uncompressed) + XCTAssertEqual(contact!.v2.keyBundle.identityKey.secp256K1Uncompressed, try alice.v1keys.identityKey.publicKey.secp256K1Uncompressed) XCTAssert(contact!.v2.keyBundle.identityKey.hasSignature == true, "no signature") XCTAssert(contact!.v2.keyBundle.preKey.hasSignature == true, "pre key not signed") @@ -474,7 +474,7 @@ final class IntegrationTests: XCTestCase { key.publicKey.secp256K1Uncompressed.bytes = Data(try LibXMTP.publicKeyFromPrivateKeyK256(privateKeyBytes: keyBytes)) let client = try await XMTPiOS.Client.create(account: key) - XCTAssertEqual(client.apiClient.environment, .dev) + XCTAssertEqual(client.environment, .dev) let conversations = try await client.conversations.list() XCTAssertEqual(1, conversations.count) @@ -542,7 +542,7 @@ final class IntegrationTests: XCTestCase { key.publicKey.secp256K1Uncompressed.bytes = Data(try LibXMTP.publicKeyFromPrivateKeyK256(privateKeyBytes: keyBytes)) let client = try await XMTPiOS.Client.create(account: key) - XCTAssertEqual(client.apiClient.environment, .dev) + XCTAssertEqual(client.environment, .dev) let convo = try await client.conversations.list()[0] let message = try await convo.messages()[0] @@ -565,7 +565,7 @@ final class IntegrationTests: XCTestCase { key.publicKey.secp256K1Uncompressed.bytes = Data(try LibXMTP.publicKeyFromPrivateKeyK256(privateKeyBytes: keyBytes)) let client = try await XMTPiOS.Client.create(account: key) - XCTAssertEqual(client.apiClient.environment, .dev) + XCTAssertEqual(client.environment, .dev) let convo = try await client.conversations.list()[0] let message = try await convo.messages().last! From 58f0c82f56843be118fa92f43ce9c8f73e2a1fce Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 20 Sep 2024 00:13:56 -0600 Subject: [PATCH 04/13] write tests for it --- Tests/XMTPTests/V3Client.swift | 68 -------------- Tests/XMTPTests/V3ClientTests.swift | 135 ++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 68 deletions(-) delete mode 100644 Tests/XMTPTests/V3Client.swift create mode 100644 Tests/XMTPTests/V3ClientTests.swift diff --git a/Tests/XMTPTests/V3Client.swift b/Tests/XMTPTests/V3Client.swift deleted file mode 100644 index f0dadb4a..00000000 --- a/Tests/XMTPTests/V3Client.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// File.swift -// -// -// Created by Naomi Plasterer on 9/19/24. -// - -import CryptoKit -import XCTest -@testable import XMTPiOS -import LibXMTP -import XMTPTestHelpers - -@available(iOS 16, *) -class V3ClientTests: XCTestCase { - // Use these fixtures to talk to the local node - struct LocalFixtures { - var alice: PrivateKey! - var bob: PrivateKey! - var fred: PrivateKey! - var aliceClient: Client! - var bobClient: Client! - var fredClient: Client! - } - - func localFixtures() async throws -> LocalFixtures { - let key = try Crypto.secureRandomBytes(count: 32) - let alice = try PrivateKey.generate() - let aliceClient = try await Client.create( - account: alice, - options: .init( - api: .init(env: .local, isSecure: false), - codecs: [GroupUpdatedCodec()], - enableV3: true, - encryptionKey: key - ) - ) - let bob = try PrivateKey.generate() - let bobClient = try await Client.create( - account: bob, - options: .init( - api: .init(env: .local, isSecure: false), - codecs: [GroupUpdatedCodec()], - enableV3: true, - encryptionKey: key - ) - ) - let fred = try PrivateKey.generate() - let fredClient = try await Client.create( - account: fred, - options: .init( - api: .init(env: .local, isSecure: false), - codecs: [GroupUpdatedCodec()], - enableV3: true, - encryptionKey: key - ) - ) - - return .init( - alice: alice, - bob: bob, - fred: fred, - aliceClient: aliceClient, - bobClient: bobClient, - fredClient: fredClient - ) - } -} diff --git a/Tests/XMTPTests/V3ClientTests.swift b/Tests/XMTPTests/V3ClientTests.swift new file mode 100644 index 00000000..1cbd5a6e --- /dev/null +++ b/Tests/XMTPTests/V3ClientTests.swift @@ -0,0 +1,135 @@ +// +// File.swift +// +// +// Created by Naomi Plasterer on 9/19/24. +// + +import CryptoKit +import XCTest +@testable import XMTPiOS +import LibXMTP +import XMTPTestHelpers + +@available(iOS 16, *) +class V3ClientTests: XCTestCase { + // Use these fixtures to talk to the local node + struct LocalFixtures { + var alixV2: PrivateKey! + var boV3: PrivateKey! + var caroV2V3: PrivateKey! + var alixV2Client: Client! + var boV3Client: Client! + var caroV2V3Client: Client! + } + + func localFixtures() async throws -> LocalFixtures { + let key = try Crypto.secureRandomBytes(count: 32) + let alixV2 = try PrivateKey.generate() + let alixV2Client = try await Client.create( + account: alixV2, + options: .init( + api: .init(env: .local, isSecure: false) + ) + ) + let boV3 = try PrivateKey.generate() + let boV3Client = try await Client.createOrBuild( + account: boV3, + options: .init( + api: .init(env: .local, isSecure: false), + enableV3: true, + encryptionKey: key + ) + ) + let caroV2V3 = try PrivateKey.generate() + let caroV2V3Client = try await Client.create( + account: caroV2V3, + options: .init( + api: .init(env: .local, isSecure: false), + enableV3: true, + encryptionKey: key + ) + ) + + return .init( + alixV2: alixV2, + boV3: boV3, + caroV2V3: caroV2V3, + alixV2Client: alixV2Client, + boV3Client: boV3Client, + caroV2V3Client: caroV2V3Client + ) + } + + func testsCanCreateGroup() async throws { + let fixtures = try await localFixtures() + let group = try await fixtures.boV3Client.conversations.newGroup(with: [fixtures.caroV2V3.address]) + let members = try group.members.map(\.inboxId).sorted() + XCTAssertEqual([fixtures.caroV2V3Client.inboxID, fixtures.boV3Client.inboxID].sorted(), members) + + await assertThrowsAsyncError( + try await fixtures.boV3Client.conversations.newGroup(with: [fixtures.alixV2.address]) + ) + } + + func testsCanSendMessages() async throws { + let fixtures = try await localFixtures() + let group = try await fixtures.boV3Client.conversations.newGroup(with: [fixtures.caroV2V3.address]) + try await group.send(content: "howdy") + let messageId = try await group.send(content: "gm") + try await group.sync() + + let groupMessages = try await group.messages() + XCTAssertEqual(groupMessages.first?.body, "gm") + XCTAssertEqual(groupMessages.first?.id, messageId) + XCTAssertEqual(groupMessages.first?.deliveryStatus, .published) + XCTAssertEqual(groupMessages.count, 3) + + + try await fixtures.caroV2V3Client.conversations.sync() + let sameGroup = try await fixtures.caroV2V3Client.conversations.groups().last + try await sameGroup?.sync() + + let sameGroupMessages = try await sameGroup?.messages() + XCTAssertEqual(sameGroupMessages?.count, 2) + XCTAssertEqual(sameGroupMessages?.first?.body, "gm") + } + + func testCanStreamAllMessagesFromV2andV3Users() async throws { + let fixtures = try await localFixtures() + + let expectation1 = XCTestExpectation(description: "got a conversation") + expectation1.expectedFulfillmentCount = 2 + let convo = try await fixtures.alixV2Client.conversations.newConversation(with: fixtures.caroV2V3.address) + let group = try await fixtures.boV3Client.conversations.newGroup(with: [fixtures.caroV2V3.address]) + try await fixtures.caroV2V3Client.conversations.sync() + Task(priority: .userInitiated) { + for try await _ in await fixtures.caroV2V3Client.conversations.streamAllMessages(includeGroups: true) { + expectation1.fulfill() + } + } + + _ = try await group.send(content: "hi") + _ = try await convo.send(content: "hi") + + await fulfillment(of: [expectation1], timeout: 3) + } + + func testCanStreamGroupsAndConversationsFromV2andV3Users() async throws { + let fixtures = try await localFixtures() + + let expectation1 = XCTestExpectation(description: "got a conversation") + expectation1.expectedFulfillmentCount = 2 + + Task(priority: .userInitiated) { + for try await _ in await fixtures.caroV2V3Client.conversations.streamAll() { + expectation1.fulfill() + } + } + + _ = try await fixtures.boV3Client.conversations.newGroup(with: [fixtures.caroV2V3.address]) + _ = try await fixtures.alixV2Client.conversations.newConversation(with: fixtures.caroV2V3.address) + + await fulfillment(of: [expectation1], timeout: 3) + } +} From f909726868198c99c20954b9c846308bbd7d1b0a Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 20 Sep 2024 07:46:11 -0600 Subject: [PATCH 05/13] fix up tests --- Tests/XMTPTests/ClientTests.swift | 26 +++++++++++++------------ Tests/XMTPTests/ConversationTests.swift | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Tests/XMTPTests/ClientTests.swift b/Tests/XMTPTests/ClientTests.swift index 43599ffe..234e6b7d 100644 --- a/Tests/XMTPTests/ClientTests.swift +++ b/Tests/XMTPTests/ClientTests.swift @@ -15,9 +15,9 @@ import XMTPTestHelpers @available(iOS 15, *) class ClientTests: XCTestCase { func testTakesAWallet() async throws { - try TestConfig.skip(because: "run manually against dev") + let opts = ClientOptions(api: ClientOptions.Api(env: .local, isSecure: false)) let fakeWallet = try PrivateKey.generate() - _ = try await Client.create(account: fakeWallet) + _ = try await Client.create(account: fakeWallet, options: opts) } func testPassingSavedKeysWithNoSignerWithMLSErrors() async throws { @@ -212,12 +212,12 @@ class ClientTests: XCTestCase { } func testCanBeCreatedWithBundle() async throws { - try TestConfig.skip(because: "run manually against dev") + let opts = ClientOptions(api: ClientOptions.Api(env: .local, isSecure: false)) let fakeWallet = try PrivateKey.generate() - let client = try await Client.create(account: fakeWallet) + let client = try await Client.create(account: fakeWallet, options: opts) let bundle = try client.privateKeyBundle - let clientFromV1Bundle = try await Client.from(bundle: bundle) + let clientFromV1Bundle = try await Client.from(bundle: bundle, options: opts) XCTAssertEqual(client.address, clientFromV1Bundle.address) XCTAssertEqual(try client.v1keys.identityKey, try clientFromV1Bundle.v1keys.identityKey) @@ -225,12 +225,12 @@ class ClientTests: XCTestCase { } func testCanBeCreatedWithV1Bundle() async throws { - try TestConfig.skip(because: "run manually against dev") + let opts = ClientOptions(api: ClientOptions.Api(env: .local, isSecure: false)) let fakeWallet = try PrivateKey.generate() - let client = try await Client.create(account: fakeWallet) + let client = try await Client.create(account: fakeWallet, options: opts) let bundleV1 = try client.v1keys - let clientFromV1Bundle = try await Client.from(v1Bundle: bundleV1) + let clientFromV1Bundle = try await Client.from(v1Bundle: bundleV1, options: opts) XCTAssertEqual(client.address, clientFromV1Bundle.address) XCTAssertEqual(try client.v1keys.identityKey, try clientFromV1Bundle.v1keys.identityKey) @@ -238,23 +238,25 @@ class ClientTests: XCTestCase { } func testCanAccessPublicKeyBundle() async throws { + let opts = ClientOptions(api: ClientOptions.Api(env: .local, isSecure: false)) let fakeWallet = try PrivateKey.generate() - let client = try await Client.create(account: fakeWallet) + let client = try await Client.create(account: fakeWallet, options: opts) let publicKeyBundle = try client.keys.getPublicKeyBundle() XCTAssertEqual(publicKeyBundle, try client.publicKeyBundle) } func testCanSignWithPrivateIdentityKey() async throws { + let opts = ClientOptions(api: ClientOptions.Api(env: .local, isSecure: false)) let fakeWallet = try PrivateKey.generate() - let client = try await Client.create(account: fakeWallet) + let client = try await Client.create(account: fakeWallet, options: opts) let digest = Util.keccak256(Data("hello world".utf8)) let signature = try await client.keys.identityKey.sign(digest) let recovered = try KeyUtilx.recoverPublicKeyKeccak256(from: signature.rawData, message: Data("hello world".utf8)) - - XCTAssertEqual(recovered, try client.keys.identityKey.publicKey.secp256K1Uncompressed.bytes) + let bytes = try client.keys.identityKey.publicKey.secp256K1Uncompressed.bytes + XCTAssertEqual(recovered, bytes) } func testPreEnableIdentityCallback() async throws { diff --git a/Tests/XMTPTests/ConversationTests.swift b/Tests/XMTPTests/ConversationTests.swift index ea7ffb8e..713829e0 100644 --- a/Tests/XMTPTests/ConversationTests.swift +++ b/Tests/XMTPTests/ConversationTests.swift @@ -97,7 +97,7 @@ class ConversationTests: XCTestCase { } func testCanStreamConversationsV2() async throws { - let options = ClientOptions(api: ClientOptions.Api(env: .dev, isSecure: true)) + let options = ClientOptions(api: ClientOptions.Api(env: .local, isSecure: false)) let wallet = try PrivateKey.generate() let client = try await Client.create(account: wallet, options: options) From 5b5fbf0a2fca5be5755a81badb6063f31a5b2715 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 20 Sep 2024 08:05:00 -0600 Subject: [PATCH 06/13] start on the consent work --- Package.swift | 2 +- Sources/XMTPiOS/Contacts.swift | 7 ++--- Sources/XMTPiOS/Extensions/Ffi.swift | 30 +++++++++++++++++++ Sources/XMTPiOS/Group.swift | 15 ++++++++++ XMTP.podspec | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +-- 6 files changed, 52 insertions(+), 8 deletions(-) diff --git a/Package.swift b/Package.swift index fe417580..f3431147 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.5.8-beta5"), + .package(url: "https://github.com/xmtp/libxmtp-swift.git", exact: "0.5.8-beta6"), ], 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/Contacts.swift b/Sources/XMTPiOS/Contacts.swift index 3f1d91f3..b6b38533 100644 --- a/Sources/XMTPiOS/Contacts.swift +++ b/Sources/XMTPiOS/Contacts.swift @@ -13,12 +13,11 @@ public typealias PrivatePreferencesAction = Xmtp_MessageContents_PrivatePreferen public enum ConsentState: String, Codable { case allowed, denied, unknown } +public enum EntryType: String, Codable { + case address, group_id, inbox_id +} public struct ConsentListEntry: Codable, Hashable { - public enum EntryType: String, Codable { - case address, group_id, inbox_id - } - static func address(_ address: String, type: ConsentState = .unknown) -> ConsentListEntry { ConsentListEntry(value: address, entryType: .address, consentType: type) } diff --git a/Sources/XMTPiOS/Extensions/Ffi.swift b/Sources/XMTPiOS/Extensions/Ffi.swift index 2f3f6d0b..fba943da 100644 --- a/Sources/XMTPiOS/Extensions/Ffi.swift +++ b/Sources/XMTPiOS/Extensions/Ffi.swift @@ -206,3 +206,33 @@ extension FfiGroupMember { Member(ffiGroupMember: self) } } + +extension ConsentState { + var toFFI: FfiConsentState{ + switch (self) { + case .allowed: FfiConsentState.allowed + case .denied: FfiConsentState.denied + default: FfiConsentState.unknown + } + } +} + +extension FfiConsentState { + var fromFFI: ConsentState{ + switch (self) { + case .allowed: ConsentState.allowed + case .denied: ConsentState.denied + default: ConsentState.unknown + } + } +} + +extension EntryType { + var toFFI: FfiConsentEntityType{ + switch (self) { + case .group_id: FfiConsentEntityType.groupId + case .inbox_id: FfiConsentEntityType.inboxId + case .address: FfiConsentEntityType.address + } + } +} diff --git a/Sources/XMTPiOS/Group.swift b/Sources/XMTPiOS/Group.swift index a255bd3a..7fd0a075 100644 --- a/Sources/XMTPiOS/Group.swift +++ b/Sources/XMTPiOS/Group.swift @@ -213,6 +213,21 @@ public struct Group: Identifiable, Equatable, Hashable { try await ffiGroup.updatePermissionPolicy(permissionUpdateType: FfiPermissionUpdateType.updateMetadata, permissionPolicyOption: PermissionOption.toFfiPermissionPolicy(option: newPermissionOption), metadataField: FfiMetadataField.pinnedFrameUrl) } + public func updateConsentState(state: ConsentState) async throws { + if (client.hasV2Client) { + switch (state) { + case .allowed: client.contacts.allowGroups(groupIds: [id]) + case .denied: client.contacts.denyGroups(groupIds: [id]) + case .unknown: Unit + } + } + + try await ffiGroup.updateConsentState(state: state.toFFI) + } + + public func consentState() throws -> ConsentState{ + return try ffiGroup.consentState().fromFFI + } public func processMessage(envelopeBytes: Data) async throws -> DecodedMessage { let message = try await ffiGroup.processStreamedGroupMessage(envelopeBytes: envelopeBytes) diff --git a/XMTP.podspec b/XMTP.podspec index b9741002..a6cde681 100644 --- a/XMTP.podspec +++ b/XMTP.podspec @@ -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.5.8-beta5' + spec.dependency 'LibXMTP', '= 0.5.8-beta6' end diff --git a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4d3d866a..73812576 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" : "abd4f896f539e5bb090c85022177d775ad08dcb1", - "version" : "0.5.8-beta4" + "revision" : "9d5153926ac1bfcab76802d5a7626c2cf47212a4", + "version" : "0.5.8-beta5" } }, { From 0cdf452360f96bf9446f2394029779b23821a865 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 20 Sep 2024 08:17:51 -0600 Subject: [PATCH 07/13] update the group --- Sources/XMTPiOS/Extensions/Ffi.swift | 6 ++++++ Sources/XMTPiOS/Group.swift | 8 ++++---- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Sources/XMTPiOS/Extensions/Ffi.swift b/Sources/XMTPiOS/Extensions/Ffi.swift index fba943da..0a97869c 100644 --- a/Sources/XMTPiOS/Extensions/Ffi.swift +++ b/Sources/XMTPiOS/Extensions/Ffi.swift @@ -236,3 +236,9 @@ extension EntryType { } } } + +extension ConsentListEntry { + var toFFI: FfiConsent { + FfiConsent(entityType: entryType.toFFI, state: consentType.toFFI, entity: value) + } +} diff --git a/Sources/XMTPiOS/Group.swift b/Sources/XMTPiOS/Group.swift index 7fd0a075..c36640fd 100644 --- a/Sources/XMTPiOS/Group.swift +++ b/Sources/XMTPiOS/Group.swift @@ -216,13 +216,13 @@ public struct Group: Identifiable, Equatable, Hashable { public func updateConsentState(state: ConsentState) async throws { if (client.hasV2Client) { switch (state) { - case .allowed: client.contacts.allowGroups(groupIds: [id]) - case .denied: client.contacts.denyGroups(groupIds: [id]) - case .unknown: Unit + case .allowed: try await client.contacts.allowGroups(groupIds: [id]) + case .denied: try await client.contacts.denyGroups(groupIds: [id]) + case .unknown: () } } - try await ffiGroup.updateConsentState(state: state.toFFI) + try ffiGroup.updateConsentState(state: state.toFFI) } public func consentState() throws -> ConsentState{ diff --git a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 73812576..b86f6275 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" : "9d5153926ac1bfcab76802d5a7626c2cf47212a4", - "version" : "0.5.8-beta5" + "revision" : "9398f5516b18044bb94e5d21dabd7a5ddfc25062", + "version" : "0.5.8-beta6" } }, { From af4b9b52a630c4a0c2750604737235eae9943cb8 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 20 Sep 2024 10:38:37 -0600 Subject: [PATCH 08/13] get all the methods and functions ready to test --- Sources/XMTPiOS/Contacts.swift | 65 +++++++++++++++++++++--------- Sources/XMTPiOS/Conversation.swift | 2 +- Sources/XMTPiOS/Group.swift | 12 ++---- Sources/XMTPiOS/Mls/Member.swift | 4 ++ 4 files changed, 56 insertions(+), 27 deletions(-) diff --git a/Sources/XMTPiOS/Contacts.swift b/Sources/XMTPiOS/Contacts.swift index d0fffe0f..33fa8df7 100644 --- a/Sources/XMTPiOS/Contacts.swift +++ b/Sources/XMTPiOS/Contacts.swift @@ -121,6 +121,9 @@ public class ConsentList { } func publish(entries: [ConsentListEntry]) async throws { + if (client.v3Client != nil) { + try await setV3ConsentState(entries: entries) + } if (client.hasV2Client) { let privateKey = try client.v1keys.identityKey.secp256K1.bytes let publicKey = try client.v1keys.identityKey.publicKey.secp256K1Uncompressed.bytes @@ -176,7 +179,11 @@ public class ConsentList { try await client.publish(envelopes: [envelope]) } - } + } + + func setV3ConsentState(entries: [ConsentListEntry]) async throws { + try await client.v3Client?.setConsentStates(records: entries.map(\.toFFI)) + } func allow(address: String) async -> ConsentListEntry { let entry = ConsentListEntry.address(address, type: ConsentState.allowed) @@ -220,7 +227,14 @@ public class ConsentList { return entry } - func state(address: String) async -> ConsentState { + func state(address: String) async throws -> ConsentState { + if let client = client.v3Client { + return try await client.getConsentState( + entityType: .address, + entity: address + ).fromFFI + } + guard let entry = await entriesManager.get(ConsentListEntry.address(address).key) else { return .unknown } @@ -228,7 +242,14 @@ public class ConsentList { return entry.consentType } - func groupState(groupId: String) async -> ConsentState { + func groupState(groupId: String) async throws -> ConsentState { + if let client = client.v3Client { + return try await client.getConsentState( + entityType: .groupId, + entity: groupId + ).fromFFI + } + guard let entry = await entriesManager.get(ConsentListEntry.groupId(groupId: groupId).key) else { return .unknown } @@ -236,7 +257,14 @@ public class ConsentList { return entry.consentType } - func inboxIdState(inboxId: String) async -> ConsentState { + func inboxIdState(inboxId: String) async throws-> ConsentState { + if let client = client.v3Client { + return try await client.getConsentState( + entityType: .inboxId, + entity: inboxId + ).fromFFI + } + guard let entry = await entriesManager.get(ConsentListEntry.inboxId(inboxId).key) else { return .unknown } @@ -262,33 +290,34 @@ public actor Contacts { consentList = ConsentList(client: client) } - public func refreshConsentList() async throws -> ConsentList { - _ = try await consentList.load() + public func refreshConsentList() async throws -> ConsentList { + let entries = try await consentList.load() + try await consentList.setV3ConsentState(entries: entries) return consentList } - public func isAllowed(_ address: String) async -> Bool { - return await consentList.state(address: address) == .allowed + public func isAllowed(_ address: String) async throws -> Bool { + return try await consentList.state(address: address) == .allowed } - public func isDenied(_ address: String) async -> Bool { - return await consentList.state(address: address) == .denied + public func isDenied(_ address: String) async throws -> Bool { + return try await consentList.state(address: address) == .denied } - public func isGroupAllowed(groupId: String) async -> Bool { - return await consentList.groupState(groupId: groupId) == .allowed + public func isGroupAllowed(groupId: String) async throws -> Bool { + return try await consentList.groupState(groupId: groupId) == .allowed } - public func isGroupDenied(groupId: String) async -> Bool { - return await consentList.groupState(groupId: groupId) == .denied + public func isGroupDenied(groupId: String) async -throws > Bool { + return try await consentList.groupState(groupId: groupId) == .denied } - public func isInboxAllowed(inboxId: String) async -> Bool { - return await consentList.inboxIdState(inboxId: inboxId) == .allowed + public func isInboxAllowed(inboxId: String) async throws -> Bool { + return try await consentList.inboxIdState(inboxId: inboxId) == .allowed } - public func isInboxDenied(inboxId: String) async -> Bool { - return await consentList.inboxIdState(inboxId: inboxId) == .denied + public func isInboxDenied(inboxId: String) async throws -> Bool { + return try await consentList.inboxIdState(inboxId: inboxId) == .denied } public func allow(addresses: [String]) async throws { diff --git a/Sources/XMTPiOS/Conversation.swift b/Sources/XMTPiOS/Conversation.swift index ad0f88ba..807a5701 100644 --- a/Sources/XMTPiOS/Conversation.swift +++ b/Sources/XMTPiOS/Conversation.swift @@ -37,7 +37,7 @@ public enum Conversation: Sendable { case .v2(let conversationV2): return try await conversationV2.client.contacts.consentList.state(address: peerAddress) case let .group(group): - return await group.client.contacts.consentList.groupState(groupId: group.id) + return try group.consentState() } } diff --git a/Sources/XMTPiOS/Group.swift b/Sources/XMTPiOS/Group.swift index c36640fd..05446058 100644 --- a/Sources/XMTPiOS/Group.swift +++ b/Sources/XMTPiOS/Group.swift @@ -245,10 +245,8 @@ public struct Group: Identifiable, Equatable, Hashable { } public func send(encodedContent: EncodedContent) async throws -> String { - let groupState = await client.contacts.consentList.groupState(groupId: id) - - if groupState == ConsentState.unknown { - try await client.contacts.allowGroups(groupIds: [id]) + if (try consentState() == .unknown) { + try await updateConsentState(state: .allowed) } let messageId = try await ffiGroup.send(contentBytes: encodedContent.serializedData()) @@ -288,10 +286,8 @@ public struct Group: Identifiable, Equatable, Hashable { } public func prepareMessage(content: T, options: SendOptions? = nil) async throws -> String { - let groupState = await client.contacts.consentList.groupState(groupId: id) - - if groupState == ConsentState.unknown { - try await client.contacts.allowGroups(groupIds: [id]) + if (try consentState() == .unknown) { + try await updateConsentState(state: .allowed) } let encodeContent = try await encodeContent(content: content, options: options) diff --git a/Sources/XMTPiOS/Mls/Member.swift b/Sources/XMTPiOS/Mls/Member.swift index a8068391..1e0885e3 100644 --- a/Sources/XMTPiOS/Mls/Member.swift +++ b/Sources/XMTPiOS/Mls/Member.swift @@ -37,5 +37,9 @@ public struct Member { return PermissionLevel.SuperAdmin } } + + public var consentState: ConsentState { + ffiGroupMember.consentState.fromFFI + } } From 3471cefac9bc4dd9cd56290ca9c40e5d54c72c07 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 20 Sep 2024 13:17:22 -0600 Subject: [PATCH 09/13] add some very involved tests --- Sources/XMTPiOS/Contacts.swift | 2 +- Sources/XMTPiOS/ConversationV1.swift | 2 +- Sources/XMTPiOS/ConversationV2.swift | 2 +- Sources/XMTPiOS/Conversations.swift | 2 +- Tests/XMTPTests/ContactsTests.swift | 20 ++++----- Tests/XMTPTests/ConversationsTest.swift | 6 +-- Tests/XMTPTests/GroupTests.swift | 24 +++++------ Tests/XMTPTests/V3ClientTests.swift | 57 +++++++++++++++++++++++++ 8 files changed, 86 insertions(+), 29 deletions(-) diff --git a/Sources/XMTPiOS/Contacts.swift b/Sources/XMTPiOS/Contacts.swift index 33fa8df7..53b84f31 100644 --- a/Sources/XMTPiOS/Contacts.swift +++ b/Sources/XMTPiOS/Contacts.swift @@ -308,7 +308,7 @@ public actor Contacts { return try await consentList.groupState(groupId: groupId) == .allowed } - public func isGroupDenied(groupId: String) async -throws > Bool { + public func isGroupDenied(groupId: String) async throws -> Bool { return try await consentList.groupState(groupId: groupId) == .denied } diff --git a/Sources/XMTPiOS/ConversationV1.swift b/Sources/XMTPiOS/ConversationV1.swift index 8e62f037..ed2c395a 100644 --- a/Sources/XMTPiOS/ConversationV1.swift +++ b/Sources/XMTPiOS/ConversationV1.swift @@ -140,7 +140,7 @@ public struct ConversationV1 { @discardableResult func send(prepared: PreparedMessage) async throws -> String { try await client.publish(envelopes: prepared.envelopes) - if((await client.contacts.consentList.state(address: peerAddress)) == .unknown) { + if((try await client.contacts.consentList.state(address: peerAddress)) == .unknown) { try await client.contacts.allow(addresses: [peerAddress]) } return prepared.messageID diff --git a/Sources/XMTPiOS/ConversationV2.swift b/Sources/XMTPiOS/ConversationV2.swift index cb479b2e..e898a485 100644 --- a/Sources/XMTPiOS/ConversationV2.swift +++ b/Sources/XMTPiOS/ConversationV2.swift @@ -256,7 +256,7 @@ public struct ConversationV2 { @discardableResult func send(prepared: PreparedMessage) async throws -> String { try await client.publish(envelopes: prepared.envelopes) - if((await client.contacts.consentList.state(address: peerAddress)) == .unknown) { + if((try await client.contacts.consentList.state(address: peerAddress)) == .unknown) { try await client.contacts.allow(addresses: [peerAddress]) } return prepared.messageID diff --git a/Sources/XMTPiOS/Conversations.swift b/Sources/XMTPiOS/Conversations.swift index 04c70cee..ad721e68 100644 --- a/Sources/XMTPiOS/Conversations.swift +++ b/Sources/XMTPiOS/Conversations.swift @@ -751,7 +751,7 @@ public actor Conversations { } let contacts = client.contacts _ = try await contacts.refreshConsentList() - if await (contacts.consentList.state(address: peerAddress) == .unknown) { + if try await (contacts.consentList.state(address: peerAddress) == .unknown) { try await contacts.allow(addresses: [peerAddress]) } } diff --git a/Tests/XMTPTests/ContactsTests.swift b/Tests/XMTPTests/ContactsTests.swift index f47caa13..6efcd574 100644 --- a/Tests/XMTPTests/ContactsTests.swift +++ b/Tests/XMTPTests/ContactsTests.swift @@ -37,13 +37,13 @@ class ContactsTests: XCTestCase { let fixtures = await fixtures() let contacts = fixtures.bobClient.contacts - var result = await contacts.isAllowed(fixtures.alice.address) + var result = try await contacts.isAllowed(fixtures.alice.address) XCTAssertFalse(result) try await contacts.allow(addresses: [fixtures.alice.address]) - result = await contacts.isAllowed(fixtures.alice.address) + result = try await contacts.isAllowed(fixtures.alice.address) XCTAssertTrue(result) } @@ -51,13 +51,13 @@ class ContactsTests: XCTestCase { let fixtures = await fixtures() let contacts = fixtures.bobClient.contacts - var result = await contacts.isAllowed(fixtures.alice.address) + var result = try await contacts.isAllowed(fixtures.alice.address) XCTAssertFalse(result) try await contacts.deny(addresses: [fixtures.alice.address]) - result = await contacts.isDenied(fixtures.alice.address) + result = try await contacts.isDenied(fixtures.alice.address) XCTAssertTrue(result) } @@ -67,21 +67,21 @@ class ContactsTests: XCTestCase { let caroClient = try await Client.create(account: caro, options: fixtures.clientOptions) let contacts = fixtures.bobClient.contacts - var result = await contacts.isAllowed(fixtures.alice.address) + var result = try await contacts.isAllowed(fixtures.alice.address) XCTAssertFalse(result) - result = await contacts.isAllowed(caroClient.address) + result = try await contacts.isAllowed(caroClient.address) XCTAssertFalse(result) try await contacts.deny(addresses: [fixtures.alice.address, caroClient.address]) - var aliceResult = await contacts.isDenied(fixtures.alice.address) + var aliceResult = try await contacts.isDenied(fixtures.alice.address) XCTAssertTrue(aliceResult) - var caroResult = await contacts.isDenied(fixtures.alice.address) + var caroResult = try await contacts.isDenied(fixtures.alice.address) XCTAssertTrue(caroResult) try await contacts.allow(addresses: [fixtures.alice.address, caroClient.address]) - aliceResult = await contacts.isAllowed(fixtures.alice.address) + aliceResult = try await contacts.isAllowed(fixtures.alice.address) XCTAssertTrue(aliceResult) - caroResult = await contacts.isAllowed(fixtures.alice.address) + caroResult = try await contacts.isAllowed(fixtures.alice.address) XCTAssertTrue(caroResult) } } diff --git a/Tests/XMTPTests/ConversationsTest.swift b/Tests/XMTPTests/ConversationsTest.swift index 90325267..ffbc5824 100644 --- a/Tests/XMTPTests/ConversationsTest.swift +++ b/Tests/XMTPTests/ConversationsTest.swift @@ -206,7 +206,7 @@ class ConversationsTests: XCTestCase { fixtures.aliceClient.conversations.list() let alixConversation = alixConversations.first(where: { $0.topic == boConversation.topic }) XCTAssertNotNil(alixConversation) - let consentStatus = await fixtures.aliceClient.contacts.isAllowed(fixtures.bobClient.address) + let consentStatus = try await fixtures.aliceClient.contacts.isAllowed(fixtures.bobClient.address) XCTAssertTrue(consentStatus) } @@ -228,7 +228,7 @@ class ConversationsTests: XCTestCase { fixtures.aliceClient.conversations.list() let alixConversation = alixConversations.first(where: { $0.topic == boConversation.topic }) XCTAssertNotNil(alixConversation) - let isDenied = await fixtures.aliceClient.contacts.isDenied(fixtures.bobClient.address) + let isDenied = try await fixtures.aliceClient.contacts.isDenied(fixtures.bobClient.address) XCTAssertTrue(isDenied) } @@ -250,7 +250,7 @@ class ConversationsTests: XCTestCase { fixtures.aliceClient.conversations.list() let alixConversation = alixConversations.first(where: { $0.topic == boConversation.topic }) XCTAssertNotNil(alixConversation) - let isAllowed = await fixtures.aliceClient.contacts.isAllowed(fixtures.bobClient.address) + let isAllowed = try await fixtures.aliceClient.contacts.isAllowed(fixtures.bobClient.address) XCTAssertFalse(isAllowed) } } diff --git a/Tests/XMTPTests/GroupTests.swift b/Tests/XMTPTests/GroupTests.swift index 7662a614..30234471 100644 --- a/Tests/XMTPTests/GroupTests.swift +++ b/Tests/XMTPTests/GroupTests.swift @@ -136,10 +136,10 @@ class GroupTests: XCTestCase { XCTAssert(!bobGroup.id.isEmpty) XCTAssert(!aliceGroup.id.isEmpty) - let bobConsentResult = await fixtures.bobClient.contacts.consentList.groupState(groupId: bobGroup.id) + let bobConsentResult = try await fixtures.bobClient.contacts.consentList.groupState(groupId: bobGroup.id) XCTAssertEqual(bobConsentResult, ConsentState.allowed) - let aliceConsentResult = await fixtures.aliceClient.contacts.consentList.groupState(groupId: aliceGroup.id) + let aliceConsentResult = try await fixtures.aliceClient.contacts.consentList.groupState(groupId: aliceGroup.id) XCTAssertEqual(aliceConsentResult, ConsentState.unknown) try await bobGroup.addMembers(addresses: [fixtures.fred.address]) @@ -420,10 +420,10 @@ class GroupTests: XCTestCase { _ = try await bobGroup.send(content: "gm") try await bobGroup.sync() - let isGroupAllowedResult = await fixtures.bobClient.contacts.isGroupAllowed(groupId: bobGroup.id) + let isGroupAllowedResult = try await fixtures.bobClient.contacts.isGroupAllowed(groupId: bobGroup.id) XCTAssertTrue(isGroupAllowedResult) - let groupStateResult = await fixtures.bobClient.contacts.consentList.groupState(groupId: bobGroup.id) + let groupStateResult = try await fixtures.bobClient.contacts.consentList.groupState(groupId: bobGroup.id) XCTAssertEqual(groupStateResult, ConsentState.allowed) } @@ -761,22 +761,22 @@ class GroupTests: XCTestCase { func testCanAllowAndDenyInboxId() async throws { let fixtures = try await localFixtures() - let isAllowed = await fixtures.bobClient.contacts.isInboxAllowed(inboxId: fixtures.aliceClient.inboxID) - let isDenied = await fixtures.bobClient.contacts.isInboxDenied(inboxId: fixtures.aliceClient.inboxID) + let isAllowed = try await fixtures.bobClient.contacts.isInboxAllowed(inboxId: fixtures.aliceClient.inboxID) + let isDenied = try await fixtures.bobClient.contacts.isInboxDenied(inboxId: fixtures.aliceClient.inboxID) XCTAssert(!isAllowed) XCTAssert(!isDenied) try await fixtures.bobClient.contacts.allowInboxes(inboxIds: [fixtures.aliceClient.inboxID]) - let isAllowed2 = await fixtures.bobClient.contacts.isInboxAllowed(inboxId: fixtures.aliceClient.inboxID) - let isDenied2 = await fixtures.bobClient.contacts.isInboxDenied(inboxId: fixtures.aliceClient.inboxID) + let isAllowed2 = try await fixtures.bobClient.contacts.isInboxAllowed(inboxId: fixtures.aliceClient.inboxID) + let isDenied2 = try await fixtures.bobClient.contacts.isInboxDenied(inboxId: fixtures.aliceClient.inboxID) XCTAssert(isAllowed2) XCTAssert(!isDenied2) try await fixtures.bobClient.contacts.denyInboxes(inboxIds: [fixtures.aliceClient.inboxID]) - let isAllowed3 = await fixtures.bobClient.contacts.isInboxAllowed(inboxId: fixtures.aliceClient.inboxID) - let isDenied3 = await fixtures.bobClient.contacts.isInboxDenied(inboxId: fixtures.aliceClient.inboxID) + let isAllowed3 = try await fixtures.bobClient.contacts.isInboxAllowed(inboxId: fixtures.aliceClient.inboxID) + let isDenied3 = try await fixtures.bobClient.contacts.isInboxDenied(inboxId: fixtures.aliceClient.inboxID) XCTAssert(!isAllowed3) XCTAssert(isDenied3) } @@ -811,10 +811,10 @@ class GroupTests: XCTestCase { try await fixtures.aliceClient.conversations.sync() let alixGroup = try fixtures.aliceClient.findGroup(groupId: boGroup.id)! - let isGroupAllowed = await fixtures.aliceClient.contacts.isGroupAllowed(groupId: boGroup.id) + let isGroupAllowed = try await fixtures.aliceClient.contacts.isGroupAllowed(groupId: boGroup.id) XCTAssert(!isGroupAllowed) let preparedMessageId = try await alixGroup.prepareMessage(content: "Test text") - let isGroupAllowed2 = await fixtures.aliceClient.contacts.isGroupAllowed(groupId: boGroup.id) + let isGroupAllowed2 = try await fixtures.aliceClient.contacts.isGroupAllowed(groupId: boGroup.id) XCTAssert(isGroupAllowed2) let messageCount = try await alixGroup.messages().count XCTAssertEqual(messageCount, 1) diff --git a/Tests/XMTPTests/V3ClientTests.swift b/Tests/XMTPTests/V3ClientTests.swift index 1cbd5a6e..614f8776 100644 --- a/Tests/XMTPTests/V3ClientTests.swift +++ b/Tests/XMTPTests/V3ClientTests.swift @@ -95,6 +95,63 @@ class V3ClientTests: XCTestCase { XCTAssertEqual(sameGroupMessages?.first?.body, "gm") } + func testGroupConsent() async throws { + let fixtures = try await localFixtures() + let group = try await fixtures.boV3Client.conversations.newGroup(with: [fixtures.caroV2V3.address]) + let isAllowed = try await fixtures.boV3Client.contacts.isGroupAllowed(groupId: group.id) + XCTAssert(isAllowed) + XCTAssertEqual(try group.consentState(), .allowed) + + try await fixtures.boV3Client.contacts.denyGroups(groupIds: [group.id]) + let isDenied = try await fixtures.boV3Client.contacts.isGroupDenied(groupId: group.id) + XCTAssert(isDenied) + XCTAssertEqual(try group.consentState(), .denied) + + try await group.updateConsentState(state: .allowed) + let isAllowed2 = try await fixtures.boV3Client.contacts.isGroupAllowed(groupId: group.id) + XCTAssert(isAllowed2) + XCTAssertEqual(try group.consentState(), .allowed) + } + + func testCanAllowAndDenyInboxId() async throws { + let fixtures = try await localFixtures() + let boGroup = try await fixtures.boV3Client.conversations.newGroup(with: [fixtures.caroV2V3.address]) + var isInboxAllowed = try await fixtures.boV3Client.contacts.isInboxAllowed(inboxId: fixtures.caroV2V3.address) + var isInboxDenied = try await fixtures.boV3Client.contacts.isInboxDenied(inboxId: fixtures.caroV2V3.address) + XCTAssert(!isInboxAllowed) + XCTAssert(!isInboxDenied) + + + try await fixtures.boV3Client.contacts.allowInboxes(inboxIds: [fixtures.caroV2V3Client.inboxID]) + var caroMember = try boGroup.members.first(where: { member in member.inboxId == fixtures.caroV2V3Client.inboxID }) + XCTAssertEqual(caroMember?.consentState, .allowed) + + isInboxAllowed = try await fixtures.boV3Client.contacts.isInboxAllowed(inboxId: fixtures.caroV2V3Client.inboxID) + XCTAssert(isInboxAllowed) + isInboxDenied = try await fixtures.boV3Client.contacts.isInboxDenied(inboxId: fixtures.caroV2V3Client.inboxID) + XCTAssert(!isInboxDenied) + var isAddressAllowed = try await fixtures.boV3Client.contacts.isAllowed(fixtures.caroV2V3Client.address) + XCTAssert(isAddressAllowed) + var isAddressDenied = try await fixtures.boV3Client.contacts.isDenied(fixtures.caroV2V3Client.address) + XCTAssert(!isAddressDenied) + + try await fixtures.boV3Client.contacts.denyInboxes(inboxIds: [fixtures.caroV2V3Client.inboxID]) + caroMember = try boGroup.members.first(where: { member in member.inboxId == fixtures.caroV2V3Client.inboxID }) + XCTAssertEqual(caroMember?.consentState, .denied) + + isInboxAllowed = try await fixtures.boV3Client.contacts.isInboxAllowed(inboxId: fixtures.caroV2V3Client.inboxID) + isInboxDenied = try await fixtures.boV3Client.contacts.isInboxDenied(inboxId: fixtures.caroV2V3Client.inboxID) + XCTAssert(!isInboxAllowed) + XCTAssert(isInboxDenied) + + try await fixtures.boV3Client.contacts.allow(addresses: [fixtures.alixV2.address]) + isAddressAllowed = try await fixtures.boV3Client.contacts.isAllowed(fixtures.alixV2.address) + isAddressDenied = try await fixtures.boV3Client.contacts.isDenied(fixtures.alixV2.address) + XCTAssert(isAddressAllowed) + XCTAssert(!isAddressDenied) + } + + func testCanStreamAllMessagesFromV2andV3Users() async throws { let fixtures = try await localFixtures() From a1d9bb67ca7df64783618b74e15170f497f2b3d6 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 20 Sep 2024 13:26:42 -0600 Subject: [PATCH 10/13] write tests for all of the funcationality --- Tests/XMTPTests/GroupTests.swift | 62 ++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/Tests/XMTPTests/GroupTests.swift b/Tests/XMTPTests/GroupTests.swift index 30234471..1d4d25e7 100644 --- a/Tests/XMTPTests/GroupTests.swift +++ b/Tests/XMTPTests/GroupTests.swift @@ -758,27 +758,61 @@ class GroupTests: XCTestCase { XCTAssertEqual(groupName, "Test Group Name 1") } + func testGroupConsent() async throws { + let fixtures = try await localFixtures() + let group = try await fixtures.bobClient.conversations.newGroup(with: [fixtures.alice.address]) + let isAllowed = try await fixtures.bobClient.contacts.isGroupAllowed(groupId: group.id) + XCTAssert(isAllowed) + XCTAssertEqual(try group.consentState(), .allowed) + + try await fixtures.bobClient.contacts.denyGroups(groupIds: [group.id]) + let isDenied = try await fixtures.bobClient.contacts.isGroupDenied(groupId: group.id) + XCTAssert(isDenied) + XCTAssertEqual(try group.consentState(), .denied) + + try await group.updateConsentState(state: .allowed) + let isAllowed2 = try await fixtures.bobClient.contacts.isGroupAllowed(groupId: group.id) + XCTAssert(isAllowed2) + XCTAssertEqual(try group.consentState(), .allowed) + } + func testCanAllowAndDenyInboxId() async throws { let fixtures = try await localFixtures() + let boGroup = try await fixtures.bobClient.conversations.newGroup(with: [fixtures.alice.address]) + var isInboxAllowed = try await fixtures.bobClient.contacts.isInboxAllowed(inboxId: fixtures.aliceClient.address) + var isInboxDenied = try await fixtures.bobClient.contacts.isInboxDenied(inboxId: fixtures.aliceClient.address) + XCTAssert(!isInboxAllowed) + XCTAssert(!isInboxDenied) - let isAllowed = try await fixtures.bobClient.contacts.isInboxAllowed(inboxId: fixtures.aliceClient.inboxID) - let isDenied = try await fixtures.bobClient.contacts.isInboxDenied(inboxId: fixtures.aliceClient.inboxID) - XCTAssert(!isAllowed) - XCTAssert(!isDenied) - + try await fixtures.bobClient.contacts.allowInboxes(inboxIds: [fixtures.aliceClient.inboxID]) + var alixMember = try boGroup.members.first(where: { member in member.inboxId == fixtures.aliceClient.inboxID }) + XCTAssertEqual(alixMember?.consentState, .allowed) - let isAllowed2 = try await fixtures.bobClient.contacts.isInboxAllowed(inboxId: fixtures.aliceClient.inboxID) - let isDenied2 = try await fixtures.bobClient.contacts.isInboxDenied(inboxId: fixtures.aliceClient.inboxID) - XCTAssert(isAllowed2) - XCTAssert(!isDenied2) + isInboxAllowed = try await fixtures.bobClient.contacts.isInboxAllowed(inboxId: fixtures.aliceClient.inboxID) + XCTAssert(isInboxAllowed) + isInboxDenied = try await fixtures.bobClient.contacts.isInboxDenied(inboxId: fixtures.aliceClient.inboxID) + XCTAssert(!isInboxDenied) + try await fixtures.bobClient.contacts.denyInboxes(inboxIds: [fixtures.aliceClient.inboxID]) - - let isAllowed3 = try await fixtures.bobClient.contacts.isInboxAllowed(inboxId: fixtures.aliceClient.inboxID) - let isDenied3 = try await fixtures.bobClient.contacts.isInboxDenied(inboxId: fixtures.aliceClient.inboxID) - XCTAssert(!isAllowed3) - XCTAssert(isDenied3) + alixMember = try boGroup.members.first(where: { member in member.inboxId == fixtures.aliceClient.inboxID }) + XCTAssertEqual(alixMember?.consentState, .denied) + + isInboxAllowed = try await fixtures.bobClient.contacts.isInboxAllowed(inboxId: fixtures.aliceClient.inboxID) + isInboxDenied = try await fixtures.bobClient.contacts.isInboxDenied(inboxId: fixtures.aliceClient.inboxID) + XCTAssert(!isInboxAllowed) + XCTAssert(isInboxDenied) + + try await fixtures.bobClient.contacts.allow(addresses: [fixtures.aliceClient.address]) + let isAddressAllowed = try await fixtures.bobClient.contacts.isAllowed(fixtures.aliceClient.address) + let isAddressDenied = try await fixtures.bobClient.contacts.isDenied(fixtures.aliceClient.address) + XCTAssert(isAddressAllowed) + XCTAssert(!isAddressDenied) + isInboxAllowed = try await fixtures.bobClient.contacts.isInboxAllowed(inboxId: fixtures.aliceClient.inboxID) + isInboxDenied = try await fixtures.bobClient.contacts.isInboxDenied(inboxId: fixtures.aliceClient.inboxID) + XCTAssert(isInboxAllowed) + XCTAssert(!isInboxDenied) } func testCanFetchGroupById() async throws { From 3c0a6909d820874a90903f88d3431ee8d6c020cb Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 20 Sep 2024 15:22:55 -0600 Subject: [PATCH 11/13] Update Package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 8 -------- 1 file changed, 8 deletions(-) diff --git a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 73ee83ff..b86f6275 100644 --- a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -59,16 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/xmtp/libxmtp-swift.git", "state" : { -<<<<<<< HEAD "revision" : "9398f5516b18044bb94e5d21dabd7a5ddfc25062", "version" : "0.5.8-beta6" -||||||| e16cd8d - "revision" : "abd4f896f539e5bb090c85022177d775ad08dcb1", - "version" : "0.5.8-beta4" -======= - "revision" : "9d5153926ac1bfcab76802d5a7626c2cf47212a4", - "version" : "0.5.8-beta5" ->>>>>>> cbfc025e7787d7eb22cdb0024004d8386468fb63 } }, { From 1691206651f7364db8a78b38a522fedfad295c0b Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 20 Sep 2024 15:25:45 -0600 Subject: [PATCH 12/13] fix up the bad merge' --- Sources/XMTPiOS/Contacts.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/XMTPiOS/Contacts.swift b/Sources/XMTPiOS/Contacts.swift index 93801c1f..c6c7e6ec 100644 --- a/Sources/XMTPiOS/Contacts.swift +++ b/Sources/XMTPiOS/Contacts.swift @@ -73,7 +73,6 @@ public class ConsentList { guard let identifier = identifier else { throw ContactError.invalidIdentifier } - let newDate = Date() let pagination = Pagination( @@ -81,7 +80,10 @@ public class ConsentList { after: lastFetched, direction: .ascending ) - let envelopes = try await client.apiClient!.envelopes(topic: Topic.preferenceList(identifier).description, pagination: pagination) + guard let apiClient = client.apiClient else { + throw ClientError.noV2Client("Error no V2 client initialized") + } + let envelopes = try await apiClient.envelopes(topic: Topic.preferenceList(identifier).description, pagination: pagination) lastFetched = newDate var preferences: [PrivatePreferencesAction] = [] From adfaf8172e0cff90edbfb1edfbe66e0001121374 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 20 Sep 2024 15:45:10 -0600 Subject: [PATCH 13/13] add returns --- Sources/XMTPiOS/Extensions/Ffi.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/XMTPiOS/Extensions/Ffi.swift b/Sources/XMTPiOS/Extensions/Ffi.swift index 0a97869c..23c58f8f 100644 --- a/Sources/XMTPiOS/Extensions/Ffi.swift +++ b/Sources/XMTPiOS/Extensions/Ffi.swift @@ -210,9 +210,9 @@ extension FfiGroupMember { extension ConsentState { var toFFI: FfiConsentState{ switch (self) { - case .allowed: FfiConsentState.allowed - case .denied: FfiConsentState.denied - default: FfiConsentState.unknown + case .allowed: return FfiConsentState.allowed + case .denied: return FfiConsentState.denied + default: return FfiConsentState.unknown } } } @@ -220,9 +220,9 @@ extension ConsentState { extension FfiConsentState { var fromFFI: ConsentState{ switch (self) { - case .allowed: ConsentState.allowed - case .denied: ConsentState.denied - default: ConsentState.unknown + case .allowed: return ConsentState.allowed + case .denied: return ConsentState.denied + default: return ConsentState.unknown } } } @@ -230,9 +230,9 @@ extension FfiConsentState { extension EntryType { var toFFI: FfiConsentEntityType{ switch (self) { - case .group_id: FfiConsentEntityType.groupId - case .inbox_id: FfiConsentEntityType.inboxId - case .address: FfiConsentEntityType.address + case .group_id: return FfiConsentEntityType.groupId + case .inbox_id: return FfiConsentEntityType.inboxId + case .address: return FfiConsentEntityType.address } } }