Skip to content

Commit

Permalink
Speed up build client performance (#450)
Browse files Browse the repository at this point in the history
* improve performance for client build

* bump pod

* allow passing the api client into create as well

* sender address needs to be senderInboxId
  • Loading branch information
nplasterer authored Dec 18, 2024
1 parent 712d873 commit 0127d21
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 72 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
}
}
```
Expand Down Expand Up @@ -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.

Expand All @@ -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)")
}
}
```
Expand Down
69 changes: 48 additions & 21 deletions Sources/XMTPiOS/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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
{
Expand All @@ -167,16 +176,18 @@ public final class Client {
accountAddress: accountAddress,
options: options,
signingKey: nil,
inboxId: resolvedInboxId
inboxId: resolvedInboxId,
apiClient: apiClient
)
}

private static func initFFiClient(
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
Expand All @@ -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,
Expand Down Expand Up @@ -236,7 +252,7 @@ public final class Client {
}
}

return (ffiClient, dbURL)
return (ffiClient, dbURL, xmtpApiClient)
}

private static func handleSignature(
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -322,14 +344,16 @@ 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
self.dbPath = dbPath
self.installationID = installationID
self.inboxID = inboxID
self.environment = environment
self.apiClient = apiClient
}

public func addAccount(newAccount: SigningKey)
Expand Down Expand Up @@ -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(
Expand All @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions Sources/XMTPiOS/DecodedMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Sources/XMTPiOS/Libxmtp/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions Tests/XMTPTests/ClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(
Expand All @@ -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."
)
}

}
2 changes: 1 addition & 1 deletion XMTP.podspec
Original file line number Diff line number Diff line change
@@ -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"

Expand Down
14 changes: 7 additions & 7 deletions XMTPiOSExample/XMTPiOSExample/Views/MessageCellView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
}
Expand All @@ -46,7 +46,7 @@ struct MessageTextView: View {
isDebugging.toggle()
}
}
if message.senderAddress.lowercased() != myAddress.lowercased() {
if message.senderInboxId.lowercased() != myAddress.lowercased() {
Spacer()
}
}
Expand All @@ -62,15 +62,15 @@ 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)
}
}

var color: Color {
if message.senderAddress.lowercased() == myAddress.lowercased() {
if message.senderInboxId.lowercased() == myAddress.lowercased() {
return .white
} else {
return .primary
Expand Down
Loading

0 comments on commit 0127d21

Please sign in to comment.