From ede81225aec813acb1061c76ffbb2fcc6b07fea3 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 13 Sep 2024 13:22:24 -0600 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 b025f93a7bc05ef0586c48feec43eeecd050254b Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 20 Sep 2024 13:58:01 -0600 Subject: [PATCH 6/7] fix the example app --- XMTPiOSExample/XMTPiOSExample/Views/ConversationListView.swift | 2 +- XMTPiOSExample/XMTPiOSExample/Views/NewConversationView.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/XMTPiOSExample/XMTPiOSExample/Views/ConversationListView.swift b/XMTPiOSExample/XMTPiOSExample/Views/ConversationListView.swift index c3f86636..99ca3171 100644 --- a/XMTPiOSExample/XMTPiOSExample/Views/ConversationListView.swift +++ b/XMTPiOSExample/XMTPiOSExample/Views/ConversationListView.swift @@ -91,7 +91,7 @@ struct ConversationListView: View { } .task { do { - for try await conversation in await client.conversations.stream() { + for try await conversation in try await client.conversations.stream() { conversations.insert(.conversation(conversation), at: 0) await add(conversations: [.conversation(conversation)]) diff --git a/XMTPiOSExample/XMTPiOSExample/Views/NewConversationView.swift b/XMTPiOSExample/XMTPiOSExample/Views/NewConversationView.swift index 8ff68aff..e538603b 100644 --- a/XMTPiOSExample/XMTPiOSExample/Views/NewConversationView.swift +++ b/XMTPiOSExample/XMTPiOSExample/Views/NewConversationView.swift @@ -25,7 +25,7 @@ enum ConversationOrGroup: Hashable { case .conversation(let conversation): return conversation.topic case .group(let group): - return group.id.toHexString() + return group.id.toHexEncodedString() } } From 7017d55458b9404b24fb56ffc3c2ad4c7637a596 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 20 Sep 2024 14:01:30 -0600 Subject: [PATCH 7/7] remove the force unwrap --- Sources/XMTPiOS/Contacts.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/XMTPiOS/Contacts.swift b/Sources/XMTPiOS/Contacts.swift index 23d55358..ebe42b0e 100644 --- a/Sources/XMTPiOS/Contacts.swift +++ b/Sources/XMTPiOS/Contacts.swift @@ -81,7 +81,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] = []