From 0127d21033393fa89c1d8ae7e4c5aeef6abe0f89 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Wed, 18 Dec 2024 08:09:06 -0800 Subject: [PATCH] Speed up build client performance (#450) * improve performance for client build * bump pod * allow passing the api client into create as well * sender address needs to be senderInboxId --- README.md | 8 +-- Sources/XMTPiOS/Client.swift | 69 +++++++++++++------ Sources/XMTPiOS/DecodedMessage.swift | 6 +- Sources/XMTPiOS/Libxmtp/Message.swift | 2 +- Tests/XMTPTests/ClientTests.swift | 31 +++++++++ XMTP.podspec | 2 +- .../Views/MessageCellView.swift | 14 ++-- dev/local/test/script.js | 35 ---------- 8 files changed, 95 insertions(+), 72 deletions(-) delete mode 100644 dev/local/test/script.js diff --git a/README.md b/README.md index 102a252c..1b71cecf 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ try await conversation.send("gm") // Listen for new messages in the conversation Task { for await message in try await conversation.streamMessages() { - print("\(message.senderAddress): \(message.body)") + print("\(message.senderInboxId): \(message.body)") } } ``` @@ -193,7 +193,7 @@ let nextPage = try await conversation.messages(limit: 25, before: messages.first You can listen for any new messages (incoming or outgoing) in a conversation by calling `conversation.streamMessages()`. -A successfully received message (that makes it through the decoding and decryption without throwing) can be trusted to be authentic. Authentic means that it was sent by the owner of the `message.senderAddress` account and that it wasn't modified in transit. The `message.sent` timestamp can be trusted to have been set by the sender. +A successfully received message (that makes it through the decoding and decryption without throwing) can be trusted to be authentic. Authentic means that it was sent by the owner of the `message.senderInboxId` account and that it wasn't modified in transit. The `message.sent` timestamp can be trusted to have been set by the sender. The flow returned by the `stream` methods is an asynchronous data stream that sequentially emits values and completes normally or with an exception. @@ -202,10 +202,10 @@ The flow returned by the `stream` methods is an asynchronous data stream that se Task { for await message in try await conversation.streamMessages() { - if message.senderAddress == client.address { + if message.senderInboxId == client.address { // This message was sent from me } - print("New message from \(message.senderAddress): \(message.body)") + print("New message from \(message.senderInboxId): \(message.body)") } } ``` diff --git a/Sources/XMTPiOS/Client.swift b/Sources/XMTPiOS/Client.swift index 96155a0b..70d6736f 100644 --- a/Sources/XMTPiOS/Client.swift +++ b/Sources/XMTPiOS/Client.swift @@ -91,6 +91,7 @@ public final class Client { public let dbPath: String public let installationID: String public let environment: XMTPEnvironment + public let apiClient: XmtpApiClient private let ffiClient: LibXMTP.FfiXmtpClient public lazy var conversations: Conversations = .init( @@ -108,13 +109,15 @@ public final class Client { accountAddress: String, options: ClientOptions, signingKey: SigningKey?, - inboxId: String + inboxId: String, + apiClient: XmtpApiClient? = nil ) async throws -> Client { - let (libxmtpClient, dbPath) = try await initFFiClient( + let (libxmtpClient, dbPath, apiClient) = try await initFFiClient( accountAddress: accountAddress.lowercased(), options: options, signingKey: signingKey, - inboxId: inboxId + inboxId: inboxId, + apiClient: apiClient ) let client = try Client( @@ -123,7 +126,8 @@ public final class Client { dbPath: dbPath, installationID: libxmtpClient.installationId().toHex, inboxID: libxmtpClient.inboxId(), - environment: options.api.env + environment: options.api.env, + apiClient: apiClient ) // Register codecs @@ -134,7 +138,10 @@ public final class Client { return client } - public static func create(account: SigningKey, options: ClientOptions) + public static func create( + account: SigningKey, options: ClientOptions, + apiClient: XmtpApiClient? = nil + ) async throws -> Client { let accountAddress = account.address.lowercased() @@ -145,12 +152,14 @@ public final class Client { accountAddress: accountAddress, options: options, signingKey: account, - inboxId: inboxId + inboxId: inboxId, + apiClient: apiClient ) } public static func build( - address: String, options: ClientOptions, inboxId: String? = nil + address: String, options: ClientOptions, inboxId: String? = nil, + apiClient: XmtpApiClient? = nil ) async throws -> Client { @@ -167,7 +176,8 @@ public final class Client { accountAddress: accountAddress, options: options, signingKey: nil, - inboxId: resolvedInboxId + inboxId: resolvedInboxId, + apiClient: apiClient ) } @@ -175,8 +185,9 @@ public final class Client { accountAddress: String, options: ClientOptions, signingKey: SigningKey?, - inboxId: String - ) async throws -> (FfiXmtpClient, String) { + inboxId: String, + apiClient: XmtpApiClient? = nil + ) async throws -> (FfiXmtpClient, String, XmtpApiClient) { let address = accountAddress.lowercased() let mlsDbDirectory = options.dbDirectory @@ -203,10 +214,15 @@ public final class Client { let alias = "xmtp-\(options.api.env.rawValue)-\(inboxId).db3" let dbURL = directoryURL.appendingPathComponent(alias).path + let xmtpApiClient: XmtpApiClient + if let existingApiClient = apiClient { + xmtpApiClient = existingApiClient + } else { + xmtpApiClient = try await connectToApiBackend(api: options.api) + } + let ffiClient = try await LibXMTP.createClient( - api: connectToBackend( - host: options.api.env.url, - isSecure: options.api.env.isSecure == true), + api: xmtpApiClient, db: dbURL, encryptionKey: options.dbEncryptionKey, inboxId: inboxId, @@ -236,7 +252,7 @@ public final class Client { } } - return (ffiClient, dbURL) + return (ffiClient, dbURL, xmtpApiClient) } private static func handleSignature( @@ -266,6 +282,14 @@ public final class Client { } } + public static func connectToApiBackend( + api: ClientOptions.Api + ) async throws -> XmtpApiClient { + return try await connectToBackend( + host: api.env.url, + isSecure: api.env.isSecure == true) + } + public static func getOrCreateInboxId( api: ClientOptions.Api, address: String ) async throws -> String { @@ -293,14 +317,12 @@ public final class Client { let address = "0x0000000000000000000000000000000000000000" let inboxId = try await getOrCreateInboxId(api: api, address: address) - var directoryURL: URL = URL.documentsDirectory + let directoryURL: URL = URL.documentsDirectory let alias = "xmtp-\(api.env.rawValue)-\(inboxId).db3" let dbURL = directoryURL.appendingPathComponent(alias).path let ffiClient = try await LibXMTP.createClient( - api: connectToBackend( - host: api.env.url, - isSecure: api.env.isSecure == true), + api: connectToApiBackend(api: api), db: dbURL, encryptionKey: nil, inboxId: inboxId, @@ -322,7 +344,8 @@ public final class Client { init( address: String, ffiClient: LibXMTP.FfiXmtpClient, dbPath: String, - installationID: String, inboxID: String, environment: XMTPEnvironment + installationID: String, inboxID: String, environment: XMTPEnvironment, + apiClient: XmtpApiClient ) throws { self.address = address self.ffiClient = ffiClient @@ -330,6 +353,7 @@ public final class Client { self.installationID = installationID self.inboxID = inboxID self.environment = environment + self.apiClient = apiClient } public func addAccount(newAccount: SigningKey) @@ -450,7 +474,8 @@ public final class Client { } } - public func findConversation(conversationId: String) async throws -> Conversation? + public func findConversation(conversationId: String) async throws + -> Conversation? { do { let conversation = try ffiClient.conversation( @@ -461,7 +486,9 @@ public final class Client { } } - public func findConversationByTopic(topic: String) async throws -> Conversation? { + public func findConversationByTopic(topic: String) async throws + -> Conversation? + { do { let regexPattern = #"/xmtp/mls/1/g-(.*?)/proto"# if let regex = try? NSRegularExpression(pattern: regexPattern) { diff --git a/Sources/XMTPiOS/DecodedMessage.swift b/Sources/XMTPiOS/DecodedMessage.swift index abe82d4b..99013cf0 100644 --- a/Sources/XMTPiOS/DecodedMessage.swift +++ b/Sources/XMTPiOS/DecodedMessage.swift @@ -9,7 +9,7 @@ public struct DecodedMessage: Sendable { public var encodedContent: EncodedContent /// The wallet address of the sender of the message - public var senderAddress: String + public var senderInboxId: String /// When the message was sent public var sent: Date @@ -24,7 +24,7 @@ public struct DecodedMessage: Sendable { client: Client, topic: String, encodedContent: EncodedContent, - senderAddress: String, + senderInboxId: String, sent: Date, sentNs: Int64, deliveryStatus: MessageDeliveryStatus = .published @@ -33,7 +33,7 @@ public struct DecodedMessage: Sendable { self.client = client self.topic = topic self.encodedContent = encodedContent - self.senderAddress = senderAddress + self.senderInboxId = senderInboxId self.sent = sent self.sentNs = sentNs self.deliveryStatus = deliveryStatus diff --git a/Sources/XMTPiOS/Libxmtp/Message.swift b/Sources/XMTPiOS/Libxmtp/Message.swift index 5ff214b3..1dd5e6d5 100644 --- a/Sources/XMTPiOS/Libxmtp/Message.swift +++ b/Sources/XMTPiOS/Libxmtp/Message.swift @@ -68,7 +68,7 @@ public struct Message: Identifiable { client: client, topic: Topic.groupMessage(convoId).description, encodedContent: encodedContent, - senderAddress: senderInboxId, + senderInboxId: senderInboxId, sent: sentAt, sentNs: sentAtNs, deliveryStatus: deliveryStatus diff --git a/Tests/XMTPTests/ClientTests.swift b/Tests/XMTPTests/ClientTests.swift index 0614f918..8127933c 100644 --- a/Tests/XMTPTests/ClientTests.swift +++ b/Tests/XMTPTests/ClientTests.swift @@ -531,6 +531,21 @@ class ClientTests: XCTestCase { let time3 = end3.timeIntervalSince(start3) print("PERF: Built a client with inboxId in \(time3)s") + // Measure time to build a client with an inboxId and apiClient + let start4 = Date() + let buildClient3 = try await Client.build( + address: fakeWallet.address, + options: ClientOptions( + api: ClientOptions.Api(env: .dev, isSecure: true), + dbEncryptionKey: key + ), + inboxId: client.inboxID, + apiClient: client.apiClient + ) + let end4 = Date() + let time4 = end4.timeIntervalSince(start4) + print("PERF: Built a client with inboxId and apiClient in \(time4)s") + // Assert performance comparisons XCTAssertTrue( time2 < time1, @@ -543,6 +558,18 @@ class ClientTests: XCTestCase { time3 < time2, "Building a client with inboxId should be faster than building one without." ) + XCTAssertTrue( + time4 < time1, + "Building a client with apiClient should be faster than creating one." + ) + XCTAssertTrue( + time4 < time2, + "Building a client with apiClient should be faster than building one." + ) + XCTAssertTrue( + time4 < time2, + "Building a client with apiClient should be faster than building one with inboxId." + ) // Assert that inbox IDs match XCTAssertEqual( @@ -553,6 +580,10 @@ class ClientTests: XCTestCase { client.inboxID, buildClient2.inboxID, "Inbox ID of the created client and second built client should match." ) + XCTAssertEqual( + client.inboxID, buildClient3.inboxID, + "Inbox ID of the created client and second built client should match." + ) } } diff --git a/XMTP.podspec b/XMTP.podspec index 15251ba3..eb213ddf 100644 --- a/XMTP.podspec +++ b/XMTP.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = "XMTP" - spec.version = "3.0.18" + spec.version = "3.0.19" spec.summary = "XMTP SDK Cocoapod" diff --git a/XMTPiOSExample/XMTPiOSExample/Views/MessageCellView.swift b/XMTPiOSExample/XMTPiOSExample/Views/MessageCellView.swift index f7483ab9..dbf128ab 100644 --- a/XMTPiOSExample/XMTPiOSExample/Views/MessageCellView.swift +++ b/XMTPiOSExample/XMTPiOSExample/Views/MessageCellView.swift @@ -17,12 +17,12 @@ struct MessageTextView: View { var body: some View { VStack { HStack { - if message.senderAddress.lowercased() == myAddress.lowercased() { + if message.senderInboxId.lowercased() == myAddress.lowercased() { Spacer() } VStack(alignment: .leading) { - if isGroup && message.senderAddress.lowercased() != myAddress.lowercased() { - Text(message.senderAddress) + if isGroup && message.senderInboxId.lowercased() != myAddress.lowercased() { + Text(message.senderInboxId) .font(.caption) .foregroundStyle(.secondary) } @@ -32,7 +32,7 @@ struct MessageTextView: View { if isDebugging { Text("My Address \(myAddress)") .font(.caption) - Text("Sender Address \(message.senderAddress)") + Text("Sender Address \(message.senderInboxId)") .font(.caption) } } @@ -46,7 +46,7 @@ struct MessageTextView: View { isDebugging.toggle() } } - if message.senderAddress.lowercased() != myAddress.lowercased() { + if message.senderInboxId.lowercased() != myAddress.lowercased() { Spacer() } } @@ -62,7 +62,7 @@ struct MessageTextView: View { } var background: Color { - if message.senderAddress.lowercased() == myAddress.lowercased() { + if message.senderInboxId.lowercased() == myAddress.lowercased() { return .purple } else { return .secondary.opacity(0.2) @@ -70,7 +70,7 @@ struct MessageTextView: View { } var color: Color { - if message.senderAddress.lowercased() == myAddress.lowercased() { + if message.senderInboxId.lowercased() == myAddress.lowercased() { return .white } else { return .primary diff --git a/dev/local/test/script.js b/dev/local/test/script.js deleted file mode 100644 index 164a446c..00000000 --- a/dev/local/test/script.js +++ /dev/null @@ -1,35 +0,0 @@ -let Client = require("@xmtp/xmtp-js").Client; -let Wallet = require("ethers").Wallet; - -console.log("NODE VERSION", process.version); - -// 0xf4BF19Ed562651837bc11ff975472ABd239D35B5 -const keyBytes = [ - 80, 7, 53, 52, 122, 163, 75, 130, 199, 86, 216, 14, 29, 2, 255, 71, 121, 51, - 165, 3, 208, 178, 193, 207, 223, 217, 75, 247, 84, 78, 204, 3, -]; - -async function checkAll() { - const wallet = new Wallet(keyBytes); - const client = await Client.create(wallet, { - apiUrl: "http://wakunode:5555", - }); - - console.log("Listening…"); - - try { - for await (const message of await client.conversations.streamAllMessages()) { - if (message.senderAddress === wallet.address) { - continue; - } - - await message.conversation.send("HI " + message.senderAddress); - console.log(`Replied to ${message.senderAddress}`); - } - } catch (e) { - console.info(`Error:`, e); - await checkAll(); - } -} - -checkAll().then(() => console.log("Done")); \ No newline at end of file