Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibXMTP Client Creation #156

Merged
merged 40 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fcb9921
first pass at all the pieces needed for threading
nplasterer Jan 19, 2024
6326d7f
a few more places
nplasterer Jan 19, 2024
ed9ae8c
make signing key extend inboxOwner
nplasterer Jan 19, 2024
ff740e1
get it decoding messages
nplasterer Jan 19, 2024
750eb84
dump the latest v3 code
nplasterer Jan 23, 2024
15db4e8
write a test for creating a v3 client
nplasterer Jan 23, 2024
88a2c77
use created At
nplasterer Jan 23, 2024
53c5897
write test for creating libxmtp client and confirm it works
nplasterer Jan 23, 2024
8998d13
move these change to a different branch
nplasterer Jan 23, 2024
397982a
dont pass a conversation
nplasterer Jan 23, 2024
7bb1534
fix linter
nplasterer Jan 23, 2024
b35f686
point to local not dev
nplasterer Jan 23, 2024
aaecfc3
feature flag the client creating of libxmtp while in alpha
nplasterer Jan 23, 2024
9bbee43
change to local
nplasterer Jan 23, 2024
14b3870
fix up the test helper
nplasterer Jan 23, 2024
5ddf0f5
feat: fix up the example app
nplasterer Jan 23, 2024
305f483
fix up the 22 compat issue
nplasterer Jan 23, 2024
2cb89b7
setup local database
nplasterer Jan 24, 2024
4892ffe
have it create correctly
nplasterer Jan 24, 2024
ca663cf
add updates to the v3 bindings
nplasterer Jan 24, 2024
1e84e4c
store in a keystore
nplasterer Jan 25, 2024
4da7d38
move to preferences
nplasterer Jan 25, 2024
c05f7f8
fix lint
nplasterer Jan 25, 2024
ca7537f
dump the latest schema
nplasterer Jan 25, 2024
3fd9099
update to the latest client creation flow
nplasterer Jan 25, 2024
89d0e63
get the create working again
nplasterer Jan 26, 2024
845d360
use the keystore because its more secure
nplasterer Jan 26, 2024
8862d42
fix up linter compat again
nplasterer Jan 26, 2024
13b7cf9
flaky test
nplasterer Jan 26, 2024
ba3c247
Merge branch 'main' of https://github.com/xmtp/xmtp-android into np/g…
nplasterer Jan 26, 2024
6710b14
reproduce the keystore bug
nplasterer Jan 26, 2024
a947a45
get the keystore actually working correctly with a test
nplasterer Jan 26, 2024
185231f
make the logger more descriptive
nplasterer Jan 26, 2024
d663056
remove all the old v2 rust stuff
nplasterer Jan 26, 2024
5fb8b51
add a check on local
nplasterer Jan 26, 2024
3ed13f1
reproduce the signing key not being the same when building from bundle
nplasterer Jan 26, 2024
44666b4
get the tests passing with a account
nplasterer Jan 26, 2024
6cc56b1
limit the amount of changes
nplasterer Jan 26, 2024
c4febaf
remove some unnecessary changes
nplasterer Jan 26, 2024
c191404
fix the linter
nplasterer Jan 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 42 additions & 6 deletions library/src/main/java/org/xmtp/android/library/Client.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ import org.xmtp.android.library.messages.walletAddress
import org.xmtp.proto.message.api.v1.MessageApiOuterClass
import org.xmtp.proto.message.api.v1.MessageApiOuterClass.BatchQueryResponse
import org.xmtp.proto.message.api.v1.MessageApiOuterClass.QueryRequest
import uniffi.xmtp_dh.FfiInboxOwner
import uniffi.xmtp_dh.FfiXmtpClient
import uniffi.xmtp_dh.createClient
import uniffi.xmtp_dh.org.xmtp.android.library.libxmtp.InboxOwner
import uniffi.xmtp_dh.org.xmtp.android.library.libxmtp.XMTPLogger
import java.nio.charset.StandardCharsets
import java.text.SimpleDateFormat
import java.time.Instant
Expand Down Expand Up @@ -63,6 +68,8 @@ class Client() {
lateinit var apiClient: ApiClient
lateinit var contacts: Contacts
lateinit var conversations: Conversations
lateinit var logger: XMTPLogger
var libXMTPClient: FfiXmtpClient? = null

companion object {
private const val TAG = "Client"
Expand Down Expand Up @@ -137,12 +144,15 @@ class Client() {
address: String,
privateKeyBundleV1: PrivateKeyBundleV1,
apiClient: ApiClient,
libXmtpClient: FfiXmtpClient? = null
) : this() {
this.address = address
this.address = libXMTPClient?.accountAddress() ?: address
this.privateKeyBundleV1 = privateKeyBundleV1
this.apiClient = apiClient
this.contacts = Contacts(client = this)
this.conversations = Conversations(client = this)
this.conversations = Conversations(client = this, libXMTPConversations = libXmtpClient?.conversations())
this.logger = XMTPLogger()
this.libXMTPClient = libXmtpClient
}

fun buildFrom(bundle: PrivateKeyBundleV1, options: ClientOptions? = null): Client {
Expand All @@ -153,18 +163,44 @@ class Client() {
return Client(address = address, privateKeyBundleV1 = bundle, apiClient = apiClient)
}

fun create(account: SigningKey, options: ClientOptions? = null): Client {
fun create(
account: SigningKey,
options: ClientOptions? = null,
inboxOwner: InboxOwner? = null,
): Client {
val clientOptions = options ?: ClientOptions()
val apiClient =
GRPCApiClient(environment = clientOptions.api.env, secure = clientOptions.api.isSecure)
return create(account = account, apiClient = apiClient, options = options)
return create(
account = account,
apiClient = apiClient,
options = options,
inboxOwner = inboxOwner
)
}

fun create(account: SigningKey, apiClient: ApiClient, options: ClientOptions? = null): Client {
fun create(
account: SigningKey,
apiClient: ApiClient,
options: ClientOptions? = null,
inboxOwner: InboxOwner? = null,
): Client {
return runBlocking {
try {
val privateKeyBundleV1 = loadOrCreateKeys(account, apiClient, options)
val client = Client(account.address, privateKeyBundleV1, apiClient)
val libXMTPClient: FfiXmtpClient? = if (inboxOwner != null) {
createClient(
logger = logger,
ffiInboxOwner = inboxOwner,
host = "https://dev.xmtp.network:5556",
isSecure = true,
db = null,
encryptionKey = null
)
} else {
null
}
val client = Client(account.address, privateKeyBundleV1, apiClient, libXMTPClient)
client.ensureUserContactPublished()
client
} catch (e: java.lang.Exception) {
Expand Down
29 changes: 28 additions & 1 deletion library/src/main/java/org/xmtp/android/library/Conversation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ sealed class Conversation {
data class V1(val conversationV1: ConversationV1) : Conversation()
data class V2(val conversationV2: ConversationV2) : Conversation()

enum class Version { V1, V2 }
data class Group(val group: org.xmtp.android.library.Group) : Conversation()

enum class Version { V1, V2, GROUP }

// This indicates whether this a v1 or v2 conversation.
val version: Version
get() {
return when (this) {
is V1 -> Version.V1
is V2 -> Version.V2
is Group -> Version.GROUP
}
}

Expand All @@ -41,6 +44,7 @@ sealed class Conversation {
return when (this) {
is V1 -> conversationV1.sentAt
is V2 -> conversationV2.createdAt
is Group -> TODO()
}
}

Expand All @@ -50,6 +54,7 @@ sealed class Conversation {
return when (this) {
is V1 -> conversationV1.peerAddress
is V2 -> conversationV2.peerAddress
is Group -> TODO()
}
}

Expand All @@ -60,6 +65,7 @@ sealed class Conversation {
return when (this) {
is V1 -> null
is V2 -> conversationV2.context.conversationId
is Group -> null
}
}

Expand All @@ -68,13 +74,15 @@ sealed class Conversation {
return when (this) {
is V1 -> null
is V2 -> conversationV2.keyMaterial
is Group -> null
}
}

fun consentState(): ConsentState {
val client: Client = when (this) {
is V1 -> conversationV1.client
is V2 -> conversationV2.client
is Group -> group.client
}
return client.contacts.consentList.state(address = peerAddress)
}
Expand All @@ -99,13 +107,15 @@ sealed class Conversation {
.setKeyMaterial(conversationV2.keyMaterial.toByteString()),
),
).build()
is Group -> TODO()
}
}

fun decode(envelope: Envelope): DecodedMessage {
return when (this) {
is V1 -> conversationV1.decode(envelope)
is V2 -> conversationV2.decodeEnvelope(envelope)
is Group -> TODO()
}
}

Expand All @@ -127,6 +137,8 @@ sealed class Conversation {
is V2 -> {
conversationV2.prepareMessage(content = content, options = options)
}

is Group -> TODO()
}
}

Expand All @@ -142,34 +154,40 @@ sealed class Conversation {
is V2 -> {
conversationV2.prepareMessage(encodedContent = encodedContent, options = options)
}

is Group -> TODO()
}
}

fun send(prepared: PreparedMessage): String {
return when (this) {
is V1 -> conversationV1.send(prepared = prepared)
is V2 -> conversationV2.send(prepared = prepared)
is Group -> TODO()
}
}

fun <T> send(content: T, options: SendOptions? = null): String {
return when (this) {
is V1 -> conversationV1.send(content = content, options = options)
is V2 -> conversationV2.send(content = content, options = options)
is Group -> TODO()
}
}

fun send(text: String, sendOptions: SendOptions? = null, sentAt: Date? = null): String {
return when (this) {
is V1 -> conversationV1.send(text = text, sendOptions, sentAt)
is V2 -> conversationV2.send(text = text, sendOptions, sentAt)
is Group -> TODO()
}
}

fun send(encodedContent: EncodedContent, options: SendOptions? = null): String {
return when (this) {
is V1 -> conversationV1.send(encodedContent = encodedContent, options = options)
is V2 -> conversationV2.send(encodedContent = encodedContent, options = options)
is Group -> TODO()
}
}

Expand All @@ -184,6 +202,7 @@ sealed class Conversation {
return when (this) {
is V1 -> conversationV1.topic.description
is V2 -> conversationV2.topic
is Group -> TODO()
}
}

Expand Down Expand Up @@ -221,6 +240,8 @@ sealed class Conversation {
after = after,
direction = direction,
)

is Group -> group.messages()
}
}

Expand All @@ -233,6 +254,7 @@ sealed class Conversation {
return when (this) {
is V1 -> conversationV1.decryptedMessages(limit, before, after, direction)
is V2 -> conversationV2.decryptedMessages(limit, before, after, direction)
is Group -> TODO()
}
}

Expand All @@ -242,6 +264,7 @@ sealed class Conversation {
return when (this) {
is V1 -> conversationV1.decrypt(envelope)
is V2 -> conversationV2.decrypt(envelope)
is Group -> TODO()
}
}

Expand All @@ -251,6 +274,7 @@ sealed class Conversation {
return when (this) {
is V1 -> conversationV1.client
is V2 -> conversationV2.client
is Group -> group.client
}
}

Expand All @@ -262,20 +286,23 @@ sealed class Conversation {
return when (this) {
is V1 -> conversationV1.streamMessages()
is V2 -> conversationV2.streamMessages()
is Group -> TODO()
}
}

fun streamDecryptedMessages(): Flow<DecryptedMessage> {
return when (this) {
is V1 -> conversationV1.streamDecryptedMessages()
is V2 -> conversationV2.streamDecryptedMessages()
is Group -> TODO()
}
}

fun streamEphemeral(): Flow<Envelope> {
return when (this) {
is V1 -> return conversationV1.streamEphemeral()
is V2 -> return conversationV2.streamEphemeral()
is Group -> TODO()
}
}
}
20 changes: 18 additions & 2 deletions library/src/main/java/org/xmtp/android/library/Conversations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ import org.xmtp.proto.keystore.api.v1.Keystore.TopicMap.TopicData
import org.xmtp.proto.message.contents.Contact
import org.xmtp.proto.message.contents.Invitation
import org.xmtp.android.library.messages.DecryptedMessage
import uniffi.xmtp_dh.FfiConversations
import java.util.Date

data class Conversations(
var client: Client,
var conversationsByTopic: MutableMap<String, Conversation> = mutableMapOf(),
val libXMTPConversations: FfiConversations? = null,
) {

companion object {
Expand Down Expand Up @@ -81,6 +83,17 @@ data class Conversations(
)
}

suspend fun newGroup(accountAddress: String): Group {
val group = libXMTPConversations?.createGroup(accountAddress)
?: throw XMTPException("Client does not support Groups")
return Group(client, group)
}

suspend fun listGroups(): List<Group> {
return libXMTPConversations?.list()?.map { Group(client, it) }
?: throw XMTPException("Client does not support Groups")
}

/**
* This creates a new [Conversation] using a specified address
* @param peerAddress The address of the client that you want to start a new conversation
Expand Down Expand Up @@ -174,7 +187,7 @@ data class Conversations(
* Get the list of conversations that current user has
* @return The list of [Conversation] that the current [Client] has.
*/
fun list(): List<Conversation> {
fun list(includeGroups: Boolean = false): List<Conversation> {
val newConversations = mutableListOf<Conversation>()
val mostRecent = conversationsByTopic.values.maxOfOrNull { it.createdAt }
val pagination = Pagination(after = mostRecent)
Expand Down Expand Up @@ -203,7 +216,10 @@ data class Conversations(
it.peerAddress != client.address && Topic.isValidTopic(it.topic)
}.map { Pair(it.topic, it) }

// TODO(perf): use DB to persist + sort
if (includeGroups) {
val groups = runBlocking { listGroups() }
// conversationsByTopic += groups.map { Pair(it.id(), Conversation.Group(it) }
}
return conversationsByTopic.values.sortedByDescending { it.createdAt }
}

Expand Down
26 changes: 26 additions & 0 deletions library/src/main/java/org/xmtp/android/library/Group.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.xmtp.android.library

import org.xmtp.android.library.libxmtp.Message
import uniffi.xmtp_dh.FfiGroup
import uniffi.xmtp_dh.FfiListMessagesOptions

class Group(val client: Client, val libXMTPGroup: FfiGroup) {
val id: List<UByte>
get() = libXMTPGroup.id()

suspend fun send(text: String) {
libXMTPGroup.send(contentBytes = text.toByteArray(Charsets.UTF_8).toUByteArray().toList())
}

suspend fun messages(): List<Message> {
return libXMTPGroup.findMessages(
opts = FfiListMessagesOptions(
sentBeforeNs = null,
sentAfterNs = null,
limit = null
)
).map {
Message(client, it)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package uniffi.xmtp_dh.org.xmtp.android.library.libxmtp

import uniffi.xmtp_dh.FfiInboxOwner

interface InboxOwner : FfiInboxOwner {}
16 changes: 16 additions & 0 deletions library/src/main/java/org/xmtp/android/library/libxmtp/Message.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.xmtp.android.library.libxmtp

import org.xmtp.android.library.Client
import uniffi.xmtp_dh.FfiMessage

data class Message(val client: Client, val libXMTPMessage: FfiMessage) {
val id: ByteArray
get() = libXMTPMessage.id

val senderAddress: String
get() = libXMTPMessage.addrFrom

fun text(): String {
return libXMTPMessage.content.decodeToString()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package uniffi.xmtp_dh.org.xmtp.android.library.libxmtp

import android.util.Log
import uniffi.xmtp_dh.FfiLogger

class XMTPLogger : FfiLogger {
override fun log(level: UInt, levelLabel: String, message: String) {
Log.i("$level $levelLabel", message)
}
}
Loading