diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2945e8ba..78ac8a4b9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Release XMTP Android Package on: push: branches: - - release + - xmtp-legacy jobs: library: diff --git a/library/src/androidTest/java/org/xmtp/android/library/DmTest.kt b/library/src/androidTest/java/org/xmtp/android/library/DmTest.kt index 4eb2617b8..be2a8b208 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/DmTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/DmTest.kt @@ -164,7 +164,7 @@ class DmTest { assertEquals(dm.messages().first().body, "gm") assertEquals(dm.messages().first().id, messageId) assertEquals(dm.messages().first().deliveryStatus, MessageDeliveryStatus.PUBLISHED) - assertEquals(dm.messages().size, 3) + assertEquals(dm.messages().size, 2) runBlocking { alixClient.conversations.syncConversations() } val sameDm = runBlocking { alixClient.conversations.listDms().last() } @@ -181,12 +181,12 @@ class DmTest { dm.send("gm") } - assertEquals(dm.messages().size, 3) - assertEquals(dm.messages(deliveryStatus = MessageDeliveryStatus.PUBLISHED).size, 3) + assertEquals(dm.messages().size, 2) + assertEquals(dm.messages(deliveryStatus = MessageDeliveryStatus.PUBLISHED).size, 2) runBlocking { dm.sync() } - assertEquals(dm.messages().size, 3) + assertEquals(dm.messages().size, 2) assertEquals(dm.messages(deliveryStatus = MessageDeliveryStatus.UNPUBLISHED).size, 0) - assertEquals(dm.messages(deliveryStatus = MessageDeliveryStatus.PUBLISHED).size, 3) + assertEquals(dm.messages(deliveryStatus = MessageDeliveryStatus.PUBLISHED).size, 2) runBlocking { alixClient.conversations.syncConversations() } val sameDm = runBlocking { alixClient.conversations.listDms().last() } @@ -219,7 +219,7 @@ class DmTest { runBlocking { dm.sync() } val messages = dm.messages() - assertEquals(messages.size, 3) + assertEquals(messages.size, 2) val content: Reaction? = messages.first().content() assertEquals("U+1F603", content?.content) assertEquals(messageToReact.id, content?.reference) diff --git a/library/src/main/java/libxmtp-version.txt b/library/src/main/java/libxmtp-version.txt index 525ce0a4b..7584779a6 100644 --- a/library/src/main/java/libxmtp-version.txt +++ b/library/src/main/java/libxmtp-version.txt @@ -1,3 +1,3 @@ -Version: 257063e9 +Version: 29a955a1 Branch: main -Date: 2024-10-29 02:56:38 +0000 +Date: 2024-11-14 22:21:21 +0000 diff --git a/library/src/main/java/org/xmtp/android/library/Client.kt b/library/src/main/java/org/xmtp/android/library/Client.kt index ed72e2609..dbb746217 100644 --- a/library/src/main/java/org/xmtp/android/library/Client.kt +++ b/library/src/main/java/org/xmtp/android/library/Client.kt @@ -37,6 +37,8 @@ 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.xmtpv3.FfiConversationType +import uniffi.xmtpv3.FfiDeviceSyncKind import uniffi.xmtpv3.FfiV2SubscribeRequest import uniffi.xmtpv3.FfiV2Subscription import uniffi.xmtpv3.FfiV2SubscriptionCallback @@ -110,10 +112,10 @@ class Client() { logger = XMTPLogger(), host = options.api.env.getUrl(), isSecure = options.api.isSecure, - accountAddress = address + accountAddress = address.lowercase() ) if (inboxId.isNullOrBlank()) { - inboxId = generateInboxId(address, 0.toULong()) + inboxId = generateInboxId(address.lowercase(), 0.toULong()) } return inboxId } @@ -188,7 +190,7 @@ class Client() { installationId: String = "", inboxId: String, ) : this() { - this.address = address + this.address = address.lowercase() this.privateKeyBundleV1 = privateKeyBundleV1 this.apiClient = apiClient this.contacts = Contacts(client = this) @@ -210,7 +212,7 @@ class Client() { inboxId: String, environment: XMTPEnvironment, ) : this() { - this.address = address + this.address = address.lowercase() this.contacts = Contacts(client = this) this.v3Client = libXMTPClient this.conversations = @@ -261,14 +263,14 @@ class Client() { apiClient, clientOptions ) - val inboxId = getOrCreateInboxId(clientOptions, account.address) + val inboxId = getOrCreateInboxId(clientOptions, account.address.lowercase()) val (libXMTPClient, dbPath) = ffiXmtpClient( clientOptions, account, clientOptions.appContext, privateKeyBundleV1, - account.address, + account.address.lowercase(), inboxId ) @@ -294,14 +296,14 @@ class Client() { clientOptions: ClientOptions, signingKey: SigningKey? = null, ): Client { - val inboxId = getOrCreateInboxId(clientOptions, accountAddress) + val inboxId = getOrCreateInboxId(clientOptions, accountAddress.lowercase()) val (libXMTPClient, dbPath) = ffiXmtpClient( clientOptions, signingKey, clientOptions.appContext, null, - accountAddress, + accountAddress.lowercase(), inboxId ) @@ -368,14 +370,14 @@ class Client() { ) newOptions.api.appVersion?.let { v2Client.setAppVersion(it) } val apiClient = GRPCApiClient(environment = newOptions.api.env, rustV2Client = v2Client) - val inboxId = getOrCreateInboxId(newOptions, address) + val inboxId = getOrCreateInboxId(newOptions, address.lowercase()) val (v3Client, dbPath) = if (isV3Enabled(options)) { ffiXmtpClient( newOptions, account, options?.appContext, v1Bundle, - address, + address.lowercase(), inboxId ) } else Pair(null, "") @@ -616,12 +618,10 @@ class Client() { fun findConversation(conversationId: String): Conversation? { val client = v3Client ?: throw XMTPException("Error no V3 client initialized") val conversation = client.conversation(conversationId.hexToByteArray()) - return if (conversation.groupMetadata().conversationType() == "dm") { - Conversation.Dm(Dm(this, conversation)) - } else if (conversation.groupMetadata().conversationType() == "group") { - Conversation.Group(Group(this, conversation)) - } else { - null + return when (conversation.conversationType()) { + FfiConversationType.GROUP -> Conversation.Group(Group(this, conversation)) + FfiConversationType.DM -> Conversation.Dm(Dm(this, conversation)) + else -> null } } @@ -631,12 +631,10 @@ class Client() { val matchResult = regex.find(topic) val conversationId = matchResult?.groupValues?.get(1) ?: "" val conversation = client.conversation(conversationId.hexToByteArray()) - return if (conversation.groupMetadata().conversationType() == "dm") { - Conversation.Dm(Dm(this, conversation)) - } else if (conversation.groupMetadata().conversationType() == "group") { - Conversation.Group(Group(this, conversation)) - } else { - null + return when (conversation.conversationType()) { + FfiConversationType.GROUP -> Conversation.Group(Group(this, conversation)) + FfiConversationType.DM -> Conversation.Dm(Dm(this, conversation)) + else -> null } } @@ -775,7 +773,7 @@ class Client() { } suspend fun requestMessageHistorySync() { - v3Client?.requestHistorySync() ?: throw XMTPException("Error no V3 client initialized") + v3Client?.sendSyncRequest(FfiDeviceSyncKind.MESSAGES) ?: throw XMTPException("Error no V3 client initialized") } suspend fun revokeAllOtherInstallations(signingKey: SigningKey) { diff --git a/library/src/main/java/org/xmtp/android/library/ConversationV1.kt b/library/src/main/java/org/xmtp/android/library/ConversationV1.kt index 2d031b5fe..9dcf67a79 100644 --- a/library/src/main/java/org/xmtp/android/library/ConversationV1.kt +++ b/library/src/main/java/org/xmtp/android/library/ConversationV1.kt @@ -27,6 +27,7 @@ import org.xmtp.android.library.messages.walletAddress import org.xmtp.proto.message.api.v1.MessageApiOuterClass import uniffi.xmtpv3.FfiEnvelope import uniffi.xmtpv3.FfiV2SubscriptionCallback +import uniffi.xmtpv3.GenericException import java.util.Date data class ConversationV1( @@ -49,6 +50,9 @@ data class ConversationV1( override fun onMessage(message: FfiEnvelope) { trySend(decode(envelope = envelopeFromFFi(message))) } + + override fun onError(error: GenericException) { + } } val stream = client.subscribe(listOf(topic.description), streamCallback) awaitClose { launch { stream.end() } } @@ -283,6 +287,9 @@ data class ConversationV1( override fun onMessage(message: FfiEnvelope) { trySend(envelopeFromFFi(message)) } + + override fun onError(error: GenericException) { + } } val stream = client.subscribe(listOf(ephemeralTopic), streamCallback) awaitClose { launch { stream.end() } } @@ -293,6 +300,9 @@ data class ConversationV1( override fun onMessage(message: FfiEnvelope) { trySend(decrypt(envelope = envelopeFromFFi(message))) } + + override fun onError(error: GenericException) { + } } val stream = client.subscribe(listOf(topic.description), streamCallback) awaitClose { launch { stream.end() } } diff --git a/library/src/main/java/org/xmtp/android/library/ConversationV2.kt b/library/src/main/java/org/xmtp/android/library/ConversationV2.kt index 981934d8f..b299665e0 100644 --- a/library/src/main/java/org/xmtp/android/library/ConversationV2.kt +++ b/library/src/main/java/org/xmtp/android/library/ConversationV2.kt @@ -24,6 +24,7 @@ import org.xmtp.proto.message.api.v1.MessageApiOuterClass import org.xmtp.proto.message.contents.Invitation import uniffi.xmtpv3.FfiEnvelope import uniffi.xmtpv3.FfiV2SubscriptionCallback +import uniffi.xmtpv3.GenericException import java.util.Date data class ConversationV2( @@ -149,6 +150,9 @@ data class ConversationV2( trySend(it) } } + + override fun onError(error: GenericException) { + } } val stream = client.subscribe(listOf(topic), streamCallback) awaitClose { launch { stream.end() } } @@ -284,6 +288,9 @@ data class ConversationV2( override fun onMessage(message: FfiEnvelope) { trySend(Util.envelopeFromFFi(message)) } + + override fun onError(error: GenericException) { + } } val stream = client.subscribe(listOf(ephemeralTopic), streamCallback) awaitClose { launch { stream.end() } } @@ -294,6 +301,9 @@ data class ConversationV2( override fun onMessage(message: FfiEnvelope) { trySend(decrypt(envelope = Util.envelopeFromFFi(message))) } + + override fun onError(error: GenericException) { + } } val stream = client.subscribe(listOf(topic), streamCallback) awaitClose { launch { stream.end() } } diff --git a/library/src/main/java/org/xmtp/android/library/Conversations.kt b/library/src/main/java/org/xmtp/android/library/Conversations.kt index a346cc710..c68e0f362 100644 --- a/library/src/main/java/org/xmtp/android/library/Conversations.kt +++ b/library/src/main/java/org/xmtp/android/library/Conversations.kt @@ -39,6 +39,7 @@ import org.xmtp.proto.message.contents.Contact import org.xmtp.proto.message.contents.Invitation import uniffi.xmtpv3.FfiConversation import uniffi.xmtpv3.FfiConversationCallback +import uniffi.xmtpv3.FfiConversationType import uniffi.xmtpv3.FfiConversations import uniffi.xmtpv3.FfiCreateGroupOptions import uniffi.xmtpv3.FfiDirection @@ -53,6 +54,7 @@ import uniffi.xmtpv3.FfiSubscribeException import uniffi.xmtpv3.FfiV2SubscribeRequest import uniffi.xmtpv3.FfiV2Subscription import uniffi.xmtpv3.FfiV2SubscriptionCallback +import uniffi.xmtpv3.GenericException import uniffi.xmtpv3.NoPointer import uniffi.xmtpv3.org.xmtp.android.library.libxmtp.GroupPermissionPreconfiguration import uniffi.xmtpv3.org.xmtp.android.library.libxmtp.PermissionPolicySet @@ -78,7 +80,7 @@ data class Conversations( suspend fun conversationFromWelcome(envelopeBytes: ByteArray): Conversation { val conversation = libXMTPConversations?.processStreamedWelcomeMessage(envelopeBytes) ?: throw XMTPException("Client does not support Groups") - if (conversation.groupMetadata().conversationType() == "dm") { + if (conversation.conversationType() == FfiConversationType.DM) { return Conversation.Dm(Dm(client, conversation)) } else { return Conversation.Group(Group(client, conversation)) @@ -312,7 +314,8 @@ data class Conversations( opts = FfiListConversationsOptions( after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), - limit?.toLong() + limit?.toLong(), + null ) ) ?: throw XMTPException("Client does not support V3 dms") @@ -331,7 +334,8 @@ data class Conversations( opts = FfiListConversationsOptions( after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), - limit?.toLong() + limit?.toLong(), + null ) ) ?: throw XMTPException("Client does not support V3 dms") @@ -354,7 +358,8 @@ data class Conversations( FfiListConversationsOptions( after?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), before?.time?.nanoseconds?.toLong(DurationUnit.NANOSECONDS), - limit?.toLong() + limit?.toLong(), + null ) ) ?: throw XMTPException("Client does not support V3 dms") @@ -404,7 +409,7 @@ data class Conversations( } private fun FfiConversation.toConversation(): Conversation { - return if (groupMetadata().conversationType() == "dm") { + return if (conversationType() == FfiConversationType.DM) { Conversation.Dm(Dm(client, this)) } else { Conversation.Group(Group(client, this)) @@ -533,6 +538,9 @@ data class Conversations( } } } + + override fun onError(error: GenericException) { + } } val stream = client.subscribe2( @@ -556,7 +564,7 @@ data class Conversations( if (client.hasV2Client) throw XMTPException("Only supported for V3 only clients.") val conversationCallback = object : FfiConversationCallback { override fun onConversation(conversation: FfiConversation) { - if (conversation.groupMetadata().conversationType() == "dm") { + if (conversation.conversationType() == FfiConversationType.DM) { trySend(Conversation.Dm(Dm(client, conversation))) } else { trySend(Conversation.Group(Group(client, conversation))) @@ -963,6 +971,9 @@ data class Conversations( else -> {} } } + + override fun onError(error: GenericException) { + } } stream = client.subscribe2(subscriptionRequest, subscriptionCallback) @@ -1013,6 +1024,9 @@ data class Conversations( else -> {} } } + + override fun onError(error: GenericException) { + } } stream = client.subscribe2(subscriptionRequest, subscriptionCallback) diff --git a/library/src/main/java/xmtpv3.kt b/library/src/main/java/xmtpv3.kt index d73c6cdd2..3039e2cb2 100644 --- a/library/src/main/java/xmtpv3.kt +++ b/library/src/main/java/xmtpv3.kt @@ -737,25 +737,34 @@ internal interface UniffiCallbackInterfaceFfiLoggerMethod0 : com.sun.jna.Callbac ) } -internal interface UniffiCallbackInterfaceFfiV2SubscriptionCallbackMethod0 : com.sun.jna.Callback { +internal interface UniffiCallbackInterfaceFfiConversationCallbackMethod0 : com.sun.jna.Callback { fun callback( `uniffiHandle`: Long, - `message`: RustBuffer.ByValue, + `conversation`: Pointer, `uniffiOutReturn`: Pointer, uniffiCallStatus: UniffiRustCallStatus, ) } -internal interface UniffiCallbackInterfaceFfiConversationCallbackMethod0 : com.sun.jna.Callback { +internal interface UniffiCallbackInterfaceFfiConversationCallbackMethod1 : com.sun.jna.Callback { fun callback( `uniffiHandle`: Long, - `conversation`: Pointer, + `error`: RustBuffer.ByValue, `uniffiOutReturn`: Pointer, uniffiCallStatus: UniffiRustCallStatus, ) } -internal interface UniffiCallbackInterfaceFfiConversationCallbackMethod1 : com.sun.jna.Callback { +internal interface UniffiCallbackInterfaceFfiMessageCallbackMethod0 : com.sun.jna.Callback { + fun callback( + `uniffiHandle`: Long, + `message`: RustBuffer.ByValue, + `uniffiOutReturn`: Pointer, + uniffiCallStatus: UniffiRustCallStatus, + ) +} + +internal interface UniffiCallbackInterfaceFfiMessageCallbackMethod1 : com.sun.jna.Callback { fun callback( `uniffiHandle`: Long, `error`: RustBuffer.ByValue, @@ -764,7 +773,7 @@ internal interface UniffiCallbackInterfaceFfiConversationCallbackMethod1 : com.s ) } -internal interface UniffiCallbackInterfaceFfiMessageCallbackMethod0 : com.sun.jna.Callback { +internal interface UniffiCallbackInterfaceFfiV2SubscriptionCallbackMethod0 : com.sun.jna.Callback { fun callback( `uniffiHandle`: Long, `message`: RustBuffer.ByValue, @@ -773,7 +782,7 @@ internal interface UniffiCallbackInterfaceFfiMessageCallbackMethod0 : com.sun.jn ) } -internal interface UniffiCallbackInterfaceFfiMessageCallbackMethod1 : com.sun.jna.Callback { +internal interface UniffiCallbackInterfaceFfiV2SubscriptionCallbackMethod1 : com.sun.jna.Callback { fun callback( `uniffiHandle`: Long, `error`: RustBuffer.ByValue, @@ -820,24 +829,6 @@ internal open class UniffiVTableCallbackInterfaceFfiLogger( } -@Structure.FieldOrder("onMessage", "uniffiFree") -internal open class UniffiVTableCallbackInterfaceFfiV2SubscriptionCallback( - @JvmField internal var `onMessage`: UniffiCallbackInterfaceFfiV2SubscriptionCallbackMethod0? = null, - @JvmField internal var `uniffiFree`: UniffiCallbackInterfaceFree? = null, -) : Structure() { - class UniffiByValue( - `onMessage`: UniffiCallbackInterfaceFfiV2SubscriptionCallbackMethod0? = null, - `uniffiFree`: UniffiCallbackInterfaceFree? = null, - ) : UniffiVTableCallbackInterfaceFfiV2SubscriptionCallback(`onMessage`, `uniffiFree`), - Structure.ByValue - - internal fun uniffiSetValue(other: UniffiVTableCallbackInterfaceFfiV2SubscriptionCallback) { - `onMessage` = other.`onMessage` - `uniffiFree` = other.`uniffiFree` - } - -} - @Structure.FieldOrder("onConversation", "onError", "uniffiFree") internal open class UniffiVTableCallbackInterfaceFfiConversationCallback( @JvmField internal var `onConversation`: UniffiCallbackInterfaceFfiConversationCallbackMethod0? = null, @@ -883,6 +874,30 @@ internal open class UniffiVTableCallbackInterfaceFfiMessageCallback( } +@Structure.FieldOrder("onMessage", "onError", "uniffiFree") +internal open class UniffiVTableCallbackInterfaceFfiV2SubscriptionCallback( + @JvmField internal var `onMessage`: UniffiCallbackInterfaceFfiV2SubscriptionCallbackMethod0? = null, + @JvmField internal var `onError`: UniffiCallbackInterfaceFfiV2SubscriptionCallbackMethod1? = null, + @JvmField internal var `uniffiFree`: UniffiCallbackInterfaceFree? = null, +) : Structure() { + class UniffiByValue( + `onMessage`: UniffiCallbackInterfaceFfiV2SubscriptionCallbackMethod0? = null, + `onError`: UniffiCallbackInterfaceFfiV2SubscriptionCallbackMethod1? = null, + `uniffiFree`: UniffiCallbackInterfaceFree? = null, + ) : UniffiVTableCallbackInterfaceFfiV2SubscriptionCallback( + `onMessage`, + `onError`, + `uniffiFree`, + ), Structure.ByValue + + internal fun uniffiSetValue(other: UniffiVTableCallbackInterfaceFfiV2SubscriptionCallback) { + `onMessage` = other.`onMessage` + `onError` = other.`onError` + `uniffiFree` = other.`uniffiFree` + } + +} + // A JNA Library to expose the extern-C FFI definitions. // This is an implementation detail which will be called internally by the public API. @@ -896,9 +911,9 @@ internal interface UniffiLib : Library { uniffiCheckApiChecksums(lib) uniffiCallbackInterfaceFfiConversationCallback.register(lib) uniffiCallbackInterfaceFfiMessageCallback.register(lib) + uniffiCallbackInterfaceFfiV2SubscriptionCallback.register(lib) uniffiCallbackInterfaceFfiInboxOwner.register(lib) uniffiCallbackInterfaceFfiLogger.register(lib) - uniffiCallbackInterfaceFfiV2SubscriptionCallback.register(lib) } } @@ -944,6 +959,10 @@ internal interface UniffiLib : Library { `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue + fun uniffi_xmtpv3_fn_method_fficonversation_conversation_type( + `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_xmtpv3_fn_method_fficonversation_created_at_ns( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): Long @@ -1288,7 +1307,7 @@ internal interface UniffiLib : Library { ): Unit fun uniffi_xmtpv3_fn_method_ffiv2apiclient_subscribe( - `ptr`: Pointer, `request`: RustBuffer.ByValue, `callback`: Long, + `ptr`: Pointer, `request`: RustBuffer.ByValue, `callback`: Pointer, ): Long fun uniffi_xmtpv3_fn_clone_ffiv2subscription( @@ -1311,6 +1330,26 @@ internal interface UniffiLib : Library { `ptr`: Pointer, `req`: RustBuffer.ByValue, ): Long + fun uniffi_xmtpv3_fn_clone_ffiv2subscriptioncallback( + `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, + ): Pointer + + fun uniffi_xmtpv3_fn_free_ffiv2subscriptioncallback( + `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun uniffi_xmtpv3_fn_init_callback_vtable_ffiv2subscriptioncallback( + `vtable`: UniffiVTableCallbackInterfaceFfiV2SubscriptionCallback, + ): Unit + + fun uniffi_xmtpv3_fn_method_ffiv2subscriptioncallback_on_message( + `ptr`: Pointer, `message`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, + ): Unit + + fun uniffi_xmtpv3_fn_method_ffiv2subscriptioncallback_on_error( + `ptr`: Pointer, `error`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_xmtpv3_fn_clone_ffixmtpclient( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): Pointer @@ -1377,6 +1416,10 @@ internal interface UniffiLib : Library { `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue + fun uniffi_xmtpv3_fn_method_ffixmtpclient_maybe_start_sync_worker( + `ptr`: Pointer, + ): Long + fun uniffi_xmtpv3_fn_method_ffixmtpclient_message( `ptr`: Pointer, `messageId`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue @@ -1389,10 +1432,6 @@ internal interface UniffiLib : Library { `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, ): Unit - fun uniffi_xmtpv3_fn_method_ffixmtpclient_request_history_sync( - `ptr`: Pointer, - ): Long - fun uniffi_xmtpv3_fn_method_ffixmtpclient_revoke_all_other_installations( `ptr`: Pointer, ): Long @@ -1401,6 +1440,10 @@ internal interface UniffiLib : Library { `ptr`: Pointer, `walletAddress`: RustBuffer.ByValue, ): Long + fun uniffi_xmtpv3_fn_method_ffixmtpclient_send_sync_request( + `ptr`: Pointer, `kind`: RustBuffer.ByValue, + ): Long + fun uniffi_xmtpv3_fn_method_ffixmtpclient_set_consent_states( `ptr`: Pointer, `records`: RustBuffer.ByValue, ): Long @@ -1417,10 +1460,6 @@ internal interface UniffiLib : Library { `vtable`: UniffiVTableCallbackInterfaceFfiLogger, ): Unit - fun uniffi_xmtpv3_fn_init_callback_vtable_ffiv2subscriptioncallback( - `vtable`: UniffiVTableCallbackInterfaceFfiV2SubscriptionCallback, - ): Unit - fun uniffi_xmtpv3_fn_func_create_client( `logger`: Long, `host`: RustBuffer.ByValue, @@ -1808,6 +1847,9 @@ internal interface UniffiLib : Library { fun uniffi_xmtpv3_checksum_method_fficonversation_consent_state( ): Short + fun uniffi_xmtpv3_checksum_method_fficonversation_conversation_type( + ): Short + fun uniffi_xmtpv3_checksum_method_fficonversation_created_at_ns( ): Short @@ -2018,6 +2060,12 @@ internal interface UniffiLib : Library { fun uniffi_xmtpv3_checksum_method_ffiv2subscription_update( ): Short + fun uniffi_xmtpv3_checksum_method_ffiv2subscriptioncallback_on_message( + ): Short + + fun uniffi_xmtpv3_checksum_method_ffiv2subscriptioncallback_on_error( + ): Short + fun uniffi_xmtpv3_checksum_method_ffixmtpclient_add_wallet( ): Short @@ -2060,6 +2108,9 @@ internal interface UniffiLib : Library { fun uniffi_xmtpv3_checksum_method_ffixmtpclient_installation_id( ): Short + fun uniffi_xmtpv3_checksum_method_ffixmtpclient_maybe_start_sync_worker( + ): Short + fun uniffi_xmtpv3_checksum_method_ffixmtpclient_message( ): Short @@ -2069,15 +2120,15 @@ internal interface UniffiLib : Library { fun uniffi_xmtpv3_checksum_method_ffixmtpclient_release_db_connection( ): Short - fun uniffi_xmtpv3_checksum_method_ffixmtpclient_request_history_sync( - ): Short - fun uniffi_xmtpv3_checksum_method_ffixmtpclient_revoke_all_other_installations( ): Short fun uniffi_xmtpv3_checksum_method_ffixmtpclient_revoke_wallet( ): Short + fun uniffi_xmtpv3_checksum_method_ffixmtpclient_send_sync_request( + ): Short + fun uniffi_xmtpv3_checksum_method_ffixmtpclient_set_consent_states( ): Short @@ -2093,9 +2144,6 @@ internal interface UniffiLib : Library { fun uniffi_xmtpv3_checksum_method_ffilogger_log( ): Short - fun uniffi_xmtpv3_checksum_method_ffiv2subscriptioncallback_on_message( - ): Short - fun ffi_xmtpv3_uniffi_contract_version( ): Int @@ -2122,7 +2170,7 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_func_diffie_hellman_k256() != 37475.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_func_generate_inbox_id() != 2184.toShort()) { + if (lib.uniffi_xmtpv3_checksum_func_generate_inbox_id() != 47637.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_xmtpv3_checksum_func_generate_private_preferences_topic_identifier() != 59124.toShort()) { @@ -2182,6 +2230,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_fficonversation_consent_state() != 25033.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_xmtpv3_checksum_method_fficonversation_conversation_type() != 16402.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_xmtpv3_checksum_method_fficonversation_created_at_ns() != 17973.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -2281,7 +2332,7 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_fficonversationcallback_on_error() != 461.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_fficonversationmetadata_conversation_type() != 48024.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_fficonversationmetadata_conversation_type() != 22241.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_xmtpv3_checksum_method_fficonversationmetadata_creator_inbox_id() != 61067.toShort()) { @@ -2380,7 +2431,7 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_ffiv2apiclient_set_app_version() != 28472.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffiv2apiclient_subscribe() != 48530.toShort()) { + if (lib.uniffi_xmtpv3_checksum_method_ffiv2apiclient_subscribe() != 39124.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_xmtpv3_checksum_method_ffiv2subscription_end() != 38721.toShort()) { @@ -2392,6 +2443,12 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_ffiv2subscription_update() != 24211.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_xmtpv3_checksum_method_ffiv2subscriptioncallback_on_message() != 30049.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_xmtpv3_checksum_method_ffiv2subscriptioncallback_on_error() != 24930.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_add_wallet() != 23786.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -2434,6 +2491,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_installation_id() != 37173.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_maybe_start_sync_worker() != 27018.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_message() != 26932.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -2443,15 +2503,15 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_release_db_connection() != 11067.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_request_history_sync() != 22295.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_revoke_all_other_installations() != 36450.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_revoke_wallet() != 12211.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_send_sync_request() != 41331.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_xmtpv3_checksum_method_ffixmtpclient_set_consent_states() != 64566.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -2467,9 +2527,6 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_xmtpv3_checksum_method_ffilogger_log() != 56011.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_xmtpv3_checksum_method_ffiv2subscriptioncallback_on_message() != 30049.toShort()) { - throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") - } } // Async support @@ -2930,6 +2987,8 @@ public interface FfiConversationInterface { fun `consentState`(): FfiConsentState + fun `conversationType`(): FfiConversationType + fun `createdAtNs`(): kotlin.Long fun `dmPeerInboxId`(): kotlin.String @@ -3261,6 +3320,20 @@ open class FfiConversation : Disposable, AutoCloseable, FfiConversationInterface } + @Throws(GenericException::class) + override fun `conversationType`(): FfiConversationType { + return FfiConverterTypeFfiConversationType.lift( + callWithPointer { + uniffiRustCallWithError(GenericException) { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversation_conversation_type( + it, _status + ) + } + } + ) + } + + override fun `createdAtNs`(): kotlin.Long { return FfiConverterLong.lift( callWithPointer { @@ -4450,7 +4523,7 @@ public object FfiConverterTypeFfiConversationCallback : public interface FfiConversationMetadataInterface { - fun `conversationType`(): kotlin.String + fun `conversationType`(): FfiConversationType fun `creatorInboxId`(): kotlin.String @@ -4538,8 +4611,8 @@ open class FfiConversationMetadata : Disposable, AutoCloseable, FfiConversationM } } - override fun `conversationType`(): kotlin.String { - return FfiConverterString.lift( + override fun `conversationType`(): FfiConversationType { + return FfiConverterTypeFfiConversationType.lift( callWithPointer { uniffiRustCall() { _status -> UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversationmetadata_conversation_type( @@ -7382,86 +7455,16 @@ public object FfiConverterTypeFfiV2Subscription : FfiConverter, - ): List - - suspend fun `applySignatureRequest`(`signatureRequest`: FfiSignatureRequest) - - suspend fun `canMessage`(`accountAddresses`: List): Map - - fun `conversation`(`conversationId`: kotlin.ByteArray): FfiConversation - - fun `conversations`(): FfiConversations - - suspend fun `dbReconnect`() - - fun `dmConversation`(`targetInboxId`: kotlin.String): FfiConversation - - suspend fun `findInboxId`(`address`: kotlin.String): kotlin.String? - - suspend fun `getConsentState`( - `entityType`: FfiConsentEntityType, - `entity`: kotlin.String, - ): FfiConsentState - - suspend fun `getLatestInboxState`(`inboxId`: kotlin.String): FfiInboxState - - fun `inboxId`(): kotlin.String - - /** - * * Get the client's inbox state. - * * - * * If `refresh_from_network` is true, the client will go to the network first to refresh the state. - * * Otherwise, the state will be read from the local database. - */ - suspend fun `inboxState`(`refreshFromNetwork`: kotlin.Boolean): FfiInboxState - - fun `installationId`(): kotlin.ByteArray - - fun `message`(`messageId`: kotlin.ByteArray): FfiMessage - - suspend fun `registerIdentity`(`signatureRequest`: FfiSignatureRequest) - - fun `releaseDbConnection`() - - suspend fun `requestHistorySync`() - - /** - * * Revokes all installations except the one the client is currently using - */ - suspend fun `revokeAllOtherInstallations`(): FfiSignatureRequest - - /** - * Revokes or removes an identity - really a wallet address - from the existing client - */ - suspend fun `revokeWallet`(`walletAddress`: kotlin.String): FfiSignatureRequest +public interface FfiV2SubscriptionCallback { - suspend fun `setConsentStates`(`records`: List) + fun `onMessage`(`message`: FfiEnvelope) - fun `signatureRequest`(): FfiSignatureRequest? + fun `onError`(`error`: GenericException) companion object } -open class FfiXmtpClient : Disposable, AutoCloseable, FfiXmtpClientInterface { +open class FfiV2SubscriptionCallbackImpl : Disposable, AutoCloseable, FfiV2SubscriptionCallback { constructor(pointer: Pointer) { this.pointer = pointer @@ -7530,7 +7533,7 @@ open class FfiXmtpClient : Disposable, AutoCloseable, FfiXmtpClientInterface { override fun run() { pointer?.let { ptr -> uniffiRustCall { status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_free_ffixmtpclient(ptr, status) + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_free_ffiv2subscriptioncallback(ptr, status) } } } @@ -7538,51 +7541,427 @@ open class FfiXmtpClient : Disposable, AutoCloseable, FfiXmtpClientInterface { fun uniffiClonePointer(): Pointer { return uniffiRustCall() { status -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_clone_ffixmtpclient(pointer!!, status) + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_clone_ffiv2subscriptioncallback(pointer!!, status) } } - - /** - * Adds an identity - really a wallet address - to the existing client - */ - @Throws(GenericException::class) - @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `addWallet`( - `existingWalletAddress`: kotlin.String, - `newWalletAddress`: kotlin.String, - ): FfiSignatureRequest { - return uniffiRustCallAsync( - callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_add_wallet( - thisPtr, - FfiConverterString.lower(`existingWalletAddress`), - FfiConverterString.lower(`newWalletAddress`), - ) - }, - { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( - future, - callback, - continuation - ) - }, - { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( - future, - continuation + override fun `onMessage`(`message`: FfiEnvelope) = + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffiv2subscriptioncallback_on_message( + it, FfiConverterTypeFfiEnvelope.lower(`message`), _status ) - }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, - // lift function - { FfiConverterTypeFfiSignatureRequest.lift(it) }, - // Error FFI converter - GenericException.ErrorHandler, - ) - } + } + } - /** + override fun `onError`(`error`: GenericException) = + callWithPointer { + uniffiRustCall() { _status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffiv2subscriptioncallback_on_error( + it, FfiConverterTypeGenericError.lower(`error`), _status + ) + } + } + + + companion object + +} + + +// Put the implementation in an object so we don't pollute the top-level namespace +internal object uniffiCallbackInterfaceFfiV2SubscriptionCallback { + internal object `onMessage` : UniffiCallbackInterfaceFfiV2SubscriptionCallbackMethod0 { + override fun callback( + `uniffiHandle`: Long, + `message`: RustBuffer.ByValue, + `uniffiOutReturn`: Pointer, + uniffiCallStatus: UniffiRustCallStatus, + ) { + val uniffiObj = FfiConverterTypeFfiV2SubscriptionCallback.handleMap.get(uniffiHandle) + val makeCall = { -> + uniffiObj.`onMessage`( + FfiConverterTypeFfiEnvelope.lift(`message`), + ) + } + val writeReturn = { _: Unit -> Unit } + uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn) + } + } + + internal object `onError` : UniffiCallbackInterfaceFfiV2SubscriptionCallbackMethod1 { + override fun callback( + `uniffiHandle`: Long, + `error`: RustBuffer.ByValue, + `uniffiOutReturn`: Pointer, + uniffiCallStatus: UniffiRustCallStatus, + ) { + val uniffiObj = FfiConverterTypeFfiV2SubscriptionCallback.handleMap.get(uniffiHandle) + val makeCall = { -> + uniffiObj.`onError`( + FfiConverterTypeGenericError.lift(`error`), + ) + } + val writeReturn = { _: Unit -> Unit } + uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn) + } + } + + internal object uniffiFree : UniffiCallbackInterfaceFree { + override fun callback(handle: Long) { + FfiConverterTypeFfiV2SubscriptionCallback.handleMap.remove(handle) + } + } + + internal var vtable = UniffiVTableCallbackInterfaceFfiV2SubscriptionCallback.UniffiByValue( + `onMessage`, + `onError`, + uniffiFree, + ) + + // Registers the foreign callback with the Rust side. + // This method is generated for each callback interface. + internal fun register(lib: UniffiLib) { + lib.uniffi_xmtpv3_fn_init_callback_vtable_ffiv2subscriptioncallback(vtable) + } +} + +/** + * @suppress + */ +public object FfiConverterTypeFfiV2SubscriptionCallback : + FfiConverter { + internal val handleMap = UniffiHandleMap() + + override fun lower(value: FfiV2SubscriptionCallback): Pointer { + return Pointer(handleMap.insert(value)) + } + + override fun lift(value: Pointer): FfiV2SubscriptionCallback { + return FfiV2SubscriptionCallbackImpl(value) + } + + override fun read(buf: ByteBuffer): FfiV2SubscriptionCallback { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: FfiV2SubscriptionCallback) = 8UL + + override fun write(value: FfiV2SubscriptionCallback, buf: ByteBuffer) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + + +public interface FfiXmtpClientInterface { + + /** + * Adds an identity - really a wallet address - to the existing client + */ + suspend fun `addWallet`( + `existingWalletAddress`: kotlin.String, + `newWalletAddress`: kotlin.String, + ): FfiSignatureRequest + + /** + * * Get the inbox state for each `inbox_id`. + * * + * * If `refresh_from_network` is true, the client will go to the network first to refresh the state. + * * Otherwise, the state will be read from the local database. + */ + suspend fun `addressesFromInboxId`( + `refreshFromNetwork`: kotlin.Boolean, + `inboxIds`: List, + ): List + + suspend fun `applySignatureRequest`(`signatureRequest`: FfiSignatureRequest) + + suspend fun `canMessage`(`accountAddresses`: List): Map + + fun `conversation`(`conversationId`: kotlin.ByteArray): FfiConversation + + fun `conversations`(): FfiConversations + + suspend fun `dbReconnect`() + + fun `dmConversation`(`targetInboxId`: kotlin.String): FfiConversation + + suspend fun `findInboxId`(`address`: kotlin.String): kotlin.String? + + suspend fun `getConsentState`( + `entityType`: FfiConsentEntityType, + `entity`: kotlin.String, + ): FfiConsentState + + suspend fun `getLatestInboxState`(`inboxId`: kotlin.String): FfiInboxState + + fun `inboxId`(): kotlin.String + + /** + * * Get the client's inbox state. + * * + * * If `refresh_from_network` is true, the client will go to the network first to refresh the state. + * * Otherwise, the state will be read from the local database. + */ + suspend fun `inboxState`(`refreshFromNetwork`: kotlin.Boolean): FfiInboxState + + fun `installationId`(): kotlin.ByteArray + + /** + * Starts the sync worker if the history sync url is present. + */ + suspend fun `maybeStartSyncWorker`() + + fun `message`(`messageId`: kotlin.ByteArray): FfiMessage + + suspend fun `registerIdentity`(`signatureRequest`: FfiSignatureRequest) + + fun `releaseDbConnection`() + + /** + * * Revokes all installations except the one the client is currently using + */ + suspend fun `revokeAllOtherInstallations`(): FfiSignatureRequest + + /** + * Revokes or removes an identity - really a wallet address - from the existing client + */ + suspend fun `revokeWallet`(`walletAddress`: kotlin.String): FfiSignatureRequest + + suspend fun `sendSyncRequest`(`kind`: FfiDeviceSyncKind) + + suspend fun `setConsentStates`(`records`: List) + + fun `signatureRequest`(): FfiSignatureRequest? + + companion object +} + +open class FfiXmtpClient : Disposable, AutoCloseable, FfiXmtpClientInterface { + + constructor(pointer: Pointer) { + this.pointer = pointer + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + /** + * This constructor can be used to instantiate a fake object. Only used for tests. Any + * attempt to actually use an object constructed this way will fail as there is no + * connected Rust object. + */ + @Suppress("UNUSED_PARAMETER") + constructor(noPointer: NoPointer) { + this.pointer = null + this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer)) + } + + protected val pointer: Pointer? + protected val cleanable: UniffiCleaner.Cleanable + + private val wasDestroyed = AtomicBoolean(false) + private val callCounter = AtomicLong(1) + + override fun destroy() { + // Only allow a single call to this method. + // TODO: maybe we should log a warning if called more than once? + if (this.wasDestroyed.compareAndSet(false, true)) { + // This decrement always matches the initial count of 1 given at creation time. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + @Synchronized + override fun close() { + this.destroy() + } + + internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (!this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction(private val pointer: Pointer?) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_free_ffixmtpclient(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer { + return uniffiRustCall() { status -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_clone_ffixmtpclient(pointer!!, status) + } + } + + + /** + * Adds an identity - really a wallet address - to the existing client + */ + @Throws(GenericException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `addWallet`( + `existingWalletAddress`: kotlin.String, + `newWalletAddress`: kotlin.String, + ): FfiSignatureRequest { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_add_wallet( + thisPtr, + FfiConverterString.lower(`existingWalletAddress`), + FfiConverterString.lower(`newWalletAddress`), + ) + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, + // lift function + { FfiConverterTypeFfiSignatureRequest.lift(it) }, + // Error FFI converter + GenericException.ErrorHandler, + ) + } + + + /** * * Get the inbox state for each `inbox_id`. * * * * If `refresh_from_network` is true, the client will go to the network first to refresh the state. @@ -7925,6 +8304,42 @@ open class FfiXmtpClient : Disposable, AutoCloseable, FfiXmtpClientInterface { } + /** + * Starts the sync worker if the history sync url is present. + */ + @Throws(GenericException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `maybeStartSyncWorker`() { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_maybe_start_sync_worker( + thisPtr, + + ) + }, + { future, callback, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + future, + callback, + continuation + ) + }, + { future, continuation -> + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + future, + continuation + ) + }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + // lift function + { Unit }, + + // Error FFI converter + GenericException.ErrorHandler, + ) + } + + @Throws(GenericException::class) override fun `message`(`messageId`: kotlin.ByteArray): FfiMessage { return FfiConverterTypeFfiMessage.lift( @@ -7983,33 +8398,35 @@ open class FfiXmtpClient : Disposable, AutoCloseable, FfiXmtpClientInterface { } + /** + * * Revokes all installations except the one the client is currently using + */ @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `requestHistorySync`() { + override suspend fun `revokeAllOtherInstallations`(): FfiSignatureRequest { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_request_history_sync( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_revoke_all_other_installations( thisPtr, ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, // lift function - { Unit }, - + { FfiConverterTypeFfiSignatureRequest.lift(it) }, // Error FFI converter GenericException.ErrorHandler, ) @@ -8017,17 +8434,17 @@ open class FfiXmtpClient : Disposable, AutoCloseable, FfiXmtpClientInterface { /** - * * Revokes all installations except the one the client is currently using + * Revokes or removes an identity - really a wallet address - from the existing client */ @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `revokeAllOtherInstallations`(): FfiSignatureRequest { + override suspend fun `revokeWallet`(`walletAddress`: kotlin.String): FfiSignatureRequest { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_revoke_all_other_installations( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_revoke_wallet( thisPtr, - - ) + FfiConverterString.lower(`walletAddress`), + ) }, { future, callback, continuation -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( @@ -8051,35 +8468,33 @@ open class FfiXmtpClient : Disposable, AutoCloseable, FfiXmtpClientInterface { } - /** - * Revokes or removes an identity - really a wallet address - from the existing client - */ @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") - override suspend fun `revokeWallet`(`walletAddress`: kotlin.String): FfiSignatureRequest { + override suspend fun `sendSyncRequest`(`kind`: FfiDeviceSyncKind) { return uniffiRustCallAsync( callWithPointer { thisPtr -> - UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_revoke_wallet( + UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_ffixmtpclient_send_sync_request( thisPtr, - FfiConverterString.lower(`walletAddress`), + FfiConverterTypeFfiDeviceSyncKind.lower(`kind`), ) }, { future, callback, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_void( future, callback, continuation ) }, { future, continuation -> - UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_pointer( + UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_void( future, continuation ) }, - { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_pointer(future) }, + { future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_void(future) }, // lift function - { FfiConverterTypeFfiSignatureRequest.lift(it) }, + { Unit }, + // Error FFI converter GenericException.ErrorHandler, ) @@ -8431,6 +8846,7 @@ data class FfiListConversationsOptions( var `createdAfterNs`: kotlin.Long?, var `createdBeforeNs`: kotlin.Long?, var `limit`: kotlin.Long?, + var `consentState`: FfiConsentState?, ) { companion object @@ -8446,19 +8862,22 @@ public object FfiConverterTypeFfiListConversationsOptions : FfiConverterOptionalLong.read(buf), FfiConverterOptionalLong.read(buf), FfiConverterOptionalLong.read(buf), + FfiConverterOptionalTypeFfiConsentState.read(buf), ) } override fun allocationSize(value: FfiListConversationsOptions) = ( FfiConverterOptionalLong.allocationSize(value.`createdAfterNs`) + FfiConverterOptionalLong.allocationSize(value.`createdBeforeNs`) + - FfiConverterOptionalLong.allocationSize(value.`limit`) + FfiConverterOptionalLong.allocationSize(value.`limit`) + + FfiConverterOptionalTypeFfiConsentState.allocationSize(value.`consentState`) ) override fun write(value: FfiListConversationsOptions, buf: ByteBuffer) { FfiConverterOptionalLong.write(value.`createdAfterNs`, buf) FfiConverterOptionalLong.write(value.`createdBeforeNs`, buf) FfiConverterOptionalLong.write(value.`limit`, buf) + FfiConverterOptionalTypeFfiConsentState.write(value.`consentState`, buf) } } @@ -8914,6 +9333,34 @@ public object FfiConverterTypeFfiConversationMessageKind : } +enum class FfiConversationType { + + GROUP, + DM, + SYNC; + + companion object +} + + +/** + * @suppress + */ +public object FfiConverterTypeFfiConversationType : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer) = try { + FfiConversationType.values()[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: FfiConversationType) = 4UL + + override fun write(value: FfiConversationType, buf: ByteBuffer) { + buf.putInt(value.ordinal + 1) + } +} + + enum class FfiDeliveryStatus { UNPUBLISHED, @@ -8942,6 +9389,33 @@ public object FfiConverterTypeFfiDeliveryStatus : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer) = try { + FfiDeviceSyncKind.values()[buf.getInt() - 1] + } catch (e: IndexOutOfBoundsException) { + throw RuntimeException("invalid enum value, something is very wrong!!", e) + } + + override fun allocationSize(value: FfiDeviceSyncKind) = 4UL + + override fun write(value: FfiDeviceSyncKind, buf: ByteBuffer) { + buf.putInt(value.ordinal + 1) + } +} + + enum class FfiDirection { ASCENDING, @@ -9213,6 +9687,10 @@ sealed class GenericException(message: String) : kotlin.Exception(message) { class FailedToConvertToU32(message: String) : GenericException(message) + class Association(message: String) : GenericException(message) + + class DeviceSync(message: String) : GenericException(message) + companion object ErrorHandler : UniffiRustCallStatusErrorHandler { override fun lift(error_buf: RustBuffer.ByValue): GenericException = @@ -9240,6 +9718,8 @@ public object FfiConverterTypeGenericError : FfiConverterRustBuffer GenericException.Erc1271SignatureException(FfiConverterString.read(buf)) 12 -> GenericException.Verifier(FfiConverterString.read(buf)) 13 -> GenericException.FailedToConvertToU32(FfiConverterString.read(buf)) + 14 -> GenericException.Association(FfiConverterString.read(buf)) + 15 -> GenericException.DeviceSync(FfiConverterString.read(buf)) else -> throw RuntimeException("invalid error enum value, something is very wrong!!") } @@ -9315,6 +9795,16 @@ public object FfiConverterTypeGenericError : FfiConverterRustBuffer { + buf.putInt(14) + Unit + } + + is GenericException.DeviceSync -> { + buf.putInt(15) + Unit + } }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } } @@ -9502,61 +9992,6 @@ internal object uniffiCallbackInterfaceFfiLogger { public object FfiConverterTypeFfiLogger : FfiConverterCallbackInterface() -public interface FfiV2SubscriptionCallback { - - fun `onMessage`(`message`: FfiEnvelope) - - companion object -} - - -// Put the implementation in an object so we don't pollute the top-level namespace -internal object uniffiCallbackInterfaceFfiV2SubscriptionCallback { - internal object `onMessage` : UniffiCallbackInterfaceFfiV2SubscriptionCallbackMethod0 { - override fun callback( - `uniffiHandle`: Long, - `message`: RustBuffer.ByValue, - `uniffiOutReturn`: Pointer, - uniffiCallStatus: UniffiRustCallStatus, - ) { - val uniffiObj = FfiConverterTypeFfiV2SubscriptionCallback.handleMap.get(uniffiHandle) - val makeCall = { -> - uniffiObj.`onMessage`( - FfiConverterTypeFfiEnvelope.lift(`message`), - ) - } - val writeReturn = { _: Unit -> Unit } - uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn) - } - } - - internal object uniffiFree : UniffiCallbackInterfaceFree { - override fun callback(handle: Long) { - FfiConverterTypeFfiV2SubscriptionCallback.handleMap.remove(handle) - } - } - - internal var vtable = UniffiVTableCallbackInterfaceFfiV2SubscriptionCallback.UniffiByValue( - `onMessage`, - uniffiFree, - ) - - // Registers the foreign callback with the Rust side. - // This method is generated for each callback interface. - internal fun register(lib: UniffiLib) { - lib.uniffi_xmtpv3_fn_init_callback_vtable_ffiv2subscriptioncallback(vtable) - } -} - -/** - * The ffiConverter which transforms the Callbacks in to handles to pass to Rust. - * - * @suppress - */ -public object FfiConverterTypeFfiV2SubscriptionCallback : - FfiConverterCallbackInterface() - - /** * @suppress */ @@ -9799,6 +10234,36 @@ public object FfiConverterOptionalTypeFfiPermissionPolicySet : } +/** + * @suppress + */ +public object FfiConverterOptionalTypeFfiConsentState : FfiConverterRustBuffer { + override fun read(buf: ByteBuffer): FfiConsentState? { + if (buf.get().toInt() == 0) { + return null + } + return FfiConverterTypeFfiConsentState.read(buf) + } + + override fun allocationSize(value: FfiConsentState?): ULong { + if (value == null) { + return 1UL + } else { + return 1UL + FfiConverterTypeFfiConsentState.allocationSize(value) + } + } + + override fun write(value: FfiConsentState?, buf: ByteBuffer) { + if (value == null) { + buf.put(0) + } else { + buf.put(1) + FfiConverterTypeFfiConsentState.write(value, buf) + } + } +} + + /** * @suppress */ @@ -10367,9 +10832,11 @@ fun `diffieHellmanK256`( ) } + +@Throws(GenericException::class) fun `generateInboxId`(`accountAddress`: kotlin.String, `nonce`: kotlin.ULong): kotlin.String { return FfiConverterString.lift( - uniffiRustCall() { _status -> + uniffiRustCallWithError(GenericException) { _status -> UniffiLib.INSTANCE.uniffi_xmtpv3_fn_func_generate_inbox_id( FfiConverterString.lower(`accountAddress`), FfiConverterULong.lower(`nonce`), diff --git a/library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so b/library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so index 9231ed8f1..a8e8ba509 100644 Binary files a/library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so and b/library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so differ diff --git a/library/src/main/jniLibs/armeabi-v7a/libuniffi_xmtpv3.so b/library/src/main/jniLibs/armeabi-v7a/libuniffi_xmtpv3.so index d665be4d3..453e13caf 100644 Binary files a/library/src/main/jniLibs/armeabi-v7a/libuniffi_xmtpv3.so and b/library/src/main/jniLibs/armeabi-v7a/libuniffi_xmtpv3.so differ diff --git a/library/src/main/jniLibs/x86/libuniffi_xmtpv3.so b/library/src/main/jniLibs/x86/libuniffi_xmtpv3.so index f9084ef1d..27ecfa2c6 100644 Binary files a/library/src/main/jniLibs/x86/libuniffi_xmtpv3.so and b/library/src/main/jniLibs/x86/libuniffi_xmtpv3.so differ diff --git a/library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so b/library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so index 2e052ea43..72faddf76 100644 Binary files a/library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so and b/library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so differ