diff --git a/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt b/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt index 08520629..da11e676 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/GroupTest.kt @@ -79,6 +79,13 @@ class GroupTest { runBlocking { alixGroup.sync() } assertEquals(alixGroup.memberAddresses().size, 3) assertEquals(boGroup.memberAddresses().size, 3) + + assertEquals(boGroup.permissionLevel(), GroupPermissions.EVERYONE_IS_ADMIN) + assertEquals(alixGroup.permissionLevel(), GroupPermissions.EVERYONE_IS_ADMIN) + assertEquals(boGroup.adminAddress().lowercase(), boClient.address.lowercase()) + assertEquals(alixGroup.adminAddress().lowercase(), boClient.address.lowercase()) + assert(boGroup.isAdmin()) + assert(!alixGroup.isAdmin()) } @Test @@ -115,6 +122,13 @@ class GroupTest { runBlocking { boGroup.sync() } assertEquals(alixGroup.memberAddresses().size, 2) assertEquals(boGroup.memberAddresses().size, 2) + + assertEquals(boGroup.permissionLevel(), GroupPermissions.GROUP_CREATOR_IS_ADMIN) + assertEquals(alixGroup.permissionLevel(), GroupPermissions.GROUP_CREATOR_IS_ADMIN) + assertEquals(boGroup.adminAddress().lowercase(), boClient.address.lowercase()) + assertEquals(alixGroup.adminAddress().lowercase(), boClient.address.lowercase()) + assert(boGroup.isAdmin()) + assert(!alixGroup.isAdmin()) } @Test diff --git a/library/src/main/java/org/xmtp/android/library/Group.kt b/library/src/main/java/org/xmtp/android/library/Group.kt index f43c4ad5..695e5fc7 100644 --- a/library/src/main/java/org/xmtp/android/library/Group.kt +++ b/library/src/main/java/org/xmtp/android/library/Group.kt @@ -12,9 +12,11 @@ import org.xmtp.android.library.messages.DecryptedMessage import org.xmtp.android.library.messages.PagingInfoSortDirection import org.xmtp.proto.message.api.v1.MessageApiOuterClass import uniffi.xmtpv3.FfiGroup +import uniffi.xmtpv3.FfiGroupMetadata import uniffi.xmtpv3.FfiListMessagesOptions import uniffi.xmtpv3.FfiMessage import uniffi.xmtpv3.FfiMessageCallback +import uniffi.xmtpv3.GroupPermissions import java.lang.Exception import java.util.Date import kotlin.time.Duration.Companion.nanoseconds @@ -27,6 +29,9 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) { val createdAt: Date get() = Date(libXMTPGroup.createdAtNs() / 1_000_000) + private val metadata: FfiGroupMetadata + get() = libXMTPGroup.groupMetadata() + fun send(text: String): String { return send(prepareMessage(content = text, options = null)) } @@ -133,6 +138,18 @@ class Group(val client: Client, private val libXMTPGroup: FfiGroup) { return libXMTPGroup.isActive() } + fun permissionLevel(): GroupPermissions { + return metadata.policyType() + } + + fun isAdmin(): Boolean { + return metadata.creatorAccountAddress().lowercase() == client.address.lowercase() + } + + fun adminAddress(): String { + return metadata.creatorAccountAddress() + } + fun addMembers(addresses: List) { try { runBlocking { libXMTPGroup.addMembers(addresses) } diff --git a/library/src/main/java/xmtpv3.kt b/library/src/main/java/xmtpv3.kt index c02eada8..414d1211 100644 --- a/library/src/main/java/xmtpv3.kt +++ b/library/src/main/java/xmtpv3.kt @@ -401,6 +401,8 @@ internal interface _UniFFILib : Library { ): Pointer fun uniffi_xmtpv3_fn_method_fficonversations_stream(`ptr`: Pointer,`callback`: Long, ): Pointer + fun uniffi_xmtpv3_fn_method_fficonversations_stream_all_messages(`ptr`: Pointer,`messageCallback`: Long, + ): Pointer fun uniffi_xmtpv3_fn_method_fficonversations_sync(`ptr`: Pointer, ): Pointer fun uniffi_xmtpv3_fn_free_ffigroup(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, @@ -411,6 +413,8 @@ internal interface _UniFFILib : Library { ): Long fun uniffi_xmtpv3_fn_method_ffigroup_find_messages(`ptr`: Pointer,`opts`: RustBuffer.ByValue,_uniffi_out_err: RustCallStatus, ): RustBuffer.ByValue + fun uniffi_xmtpv3_fn_method_ffigroup_group_metadata(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Pointer fun uniffi_xmtpv3_fn_method_ffigroup_id(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, ): RustBuffer.ByValue fun uniffi_xmtpv3_fn_method_ffigroup_is_active(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, @@ -425,6 +429,14 @@ internal interface _UniFFILib : Library { ): Pointer fun uniffi_xmtpv3_fn_method_ffigroup_sync(`ptr`: Pointer, ): Pointer + fun uniffi_xmtpv3_fn_free_ffigroupmetadata(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): Unit + fun uniffi_xmtpv3_fn_method_ffigroupmetadata_conversation_type(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun uniffi_xmtpv3_fn_method_ffigroupmetadata_creator_account_address(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue + fun uniffi_xmtpv3_fn_method_ffigroupmetadata_policy_type(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, + ): RustBuffer.ByValue fun uniffi_xmtpv3_fn_free_ffistreamcloser(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, ): Unit fun uniffi_xmtpv3_fn_method_ffistreamcloser_end(`ptr`: Pointer,_uniffi_out_err: RustCallStatus, @@ -647,6 +659,8 @@ internal interface _UniFFILib : Library { ): Short fun uniffi_xmtpv3_checksum_method_fficonversations_stream( ): Short + fun uniffi_xmtpv3_checksum_method_fficonversations_stream_all_messages( + ): Short fun uniffi_xmtpv3_checksum_method_fficonversations_sync( ): Short fun uniffi_xmtpv3_checksum_method_ffigroup_add_members( @@ -655,6 +669,8 @@ internal interface _UniFFILib : Library { ): Short fun uniffi_xmtpv3_checksum_method_ffigroup_find_messages( ): Short + fun uniffi_xmtpv3_checksum_method_ffigroup_group_metadata( + ): Short fun uniffi_xmtpv3_checksum_method_ffigroup_id( ): Short fun uniffi_xmtpv3_checksum_method_ffigroup_is_active( @@ -669,6 +685,12 @@ internal interface _UniFFILib : Library { ): Short fun uniffi_xmtpv3_checksum_method_ffigroup_sync( ): Short + fun uniffi_xmtpv3_checksum_method_ffigroupmetadata_conversation_type( + ): Short + fun uniffi_xmtpv3_checksum_method_ffigroupmetadata_creator_account_address( + ): Short + fun uniffi_xmtpv3_checksum_method_ffigroupmetadata_policy_type( + ): Short fun uniffi_xmtpv3_checksum_method_ffistreamcloser_end( ): Short fun uniffi_xmtpv3_checksum_method_ffistreamcloser_is_closed( @@ -777,6 +799,9 @@ private fun uniffiCheckApiChecksums(lib: _UniFFILib) { if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream() != 60583.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_xmtpv3_checksum_method_fficonversations_stream_all_messages() != 65211.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_xmtpv3_checksum_method_fficonversations_sync() != 62598.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -789,6 +814,9 @@ private fun uniffiCheckApiChecksums(lib: _UniFFILib) { if (lib.uniffi_xmtpv3_checksum_method_ffigroup_find_messages() != 61973.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_xmtpv3_checksum_method_ffigroup_group_metadata() != 3690.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_xmtpv3_checksum_method_ffigroup_id() != 35243.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -810,6 +838,15 @@ private fun uniffiCheckApiChecksums(lib: _UniFFILib) { if (lib.uniffi_xmtpv3_checksum_method_ffigroup_sync() != 9422.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_xmtpv3_checksum_method_ffigroupmetadata_conversation_type() != 37015.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_xmtpv3_checksum_method_ffigroupmetadata_creator_account_address() != 1906.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_xmtpv3_checksum_method_ffigroupmetadata_policy_type() != 22845.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_xmtpv3_checksum_method_ffistreamcloser_end() != 47211.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -1259,6 +1296,7 @@ public interface FfiConversationsInterface { suspend fun `createGroup`(`accountAddresses`: List, `permissions`: GroupPermissions?): FfiGroup@Throws(GenericException::class) suspend fun `list`(`opts`: FfiListConversationsOptions): List@Throws(GenericException::class) suspend fun `stream`(`callback`: FfiConversationCallback): FfiStreamCloser@Throws(GenericException::class) + suspend fun `streamAllMessages`(`messageCallback`: FfiMessageCallback): FfiStreamCloser@Throws(GenericException::class) suspend fun `sync`() companion object } @@ -1342,6 +1380,26 @@ class FfiConversations( ) } + @Throws(GenericException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `streamAllMessages`(`messageCallback`: FfiMessageCallback) : FfiStreamCloser { + return uniffiRustCallAsync( + callWithPointer { thisPtr -> + _UniFFILib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_stream_all_messages( + thisPtr, + FfiConverterTypeFfiMessageCallback.lower(`messageCallback`), + ) + }, + { future, continuation -> _UniFFILib.INSTANCE.ffi_xmtpv3_rust_future_poll_pointer(future, 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 + { FfiConverterTypeFfiStreamCloser.lift(it) }, + // Error FFI converter + GenericException.ErrorHandler, + ) + } + @Throws(GenericException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") override suspend fun `sync`() { @@ -1398,7 +1456,8 @@ public interface FfiGroupInterface { @Throws(GenericException::class) suspend fun `addMembers`(`accountAddresses`: List) fun `createdAtNs`(): Long@Throws(GenericException::class) - fun `findMessages`(`opts`: FfiListMessagesOptions): List + fun `findMessages`(`opts`: FfiListMessagesOptions): List@Throws(GenericException::class) + fun `groupMetadata`(): FfiGroupMetadata fun `id`(): ByteArray@Throws(GenericException::class) fun `isActive`(): Boolean@Throws(GenericException::class) fun `listMembers`(): List@Throws(GenericException::class) @@ -1471,6 +1530,18 @@ class FfiGroup( FfiConverterSequenceTypeFfiMessage.lift(it) } + + @Throws(GenericException::class)override fun `groupMetadata`(): FfiGroupMetadata = + callWithPointer { + rustCallWithError(GenericException) { _status -> + _UniFFILib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroup_group_metadata(it, + + _status) + } + }.let { + FfiConverterTypeFfiGroupMetadata.lift(it) + } + override fun `id`(): ByteArray = callWithPointer { rustCall() { _status -> @@ -1621,6 +1692,98 @@ public object FfiConverterTypeFfiGroup: FfiConverter { +public interface FfiGroupMetadataInterface { + + fun `conversationType`(): String + fun `creatorAccountAddress`(): String@Throws(GenericException::class) + fun `policyType`(): GroupPermissions + companion object +} + +class FfiGroupMetadata( + pointer: Pointer +) : FFIObject(pointer), FfiGroupMetadataInterface { + + /** + * Disconnect the object from the underlying Rust object. + * + * It can be called more than once, but once called, interacting with the object + * causes an `IllegalStateException`. + * + * Clients **must** call this method once done with the object, or cause a memory leak. + */ + override protected fun freeRustArcPtr() { + rustCall() { status -> + _UniFFILib.INSTANCE.uniffi_xmtpv3_fn_free_ffigroupmetadata(this.pointer, status) + } + } + + override fun `conversationType`(): String = + callWithPointer { + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroupmetadata_conversation_type(it, + + _status) + } + }.let { + FfiConverterString.lift(it) + } + + override fun `creatorAccountAddress`(): String = + callWithPointer { + rustCall() { _status -> + _UniFFILib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroupmetadata_creator_account_address(it, + + _status) + } + }.let { + FfiConverterString.lift(it) + } + + + @Throws(GenericException::class)override fun `policyType`(): GroupPermissions = + callWithPointer { + rustCallWithError(GenericException) { _status -> + _UniFFILib.INSTANCE.uniffi_xmtpv3_fn_method_ffigroupmetadata_policy_type(it, + + _status) + } + }.let { + FfiConverterTypeGroupPermissions.lift(it) + } + + + + + companion object + +} + +public object FfiConverterTypeFfiGroupMetadata: FfiConverter { + override fun lower(value: FfiGroupMetadata): Pointer = value.callWithPointer { it } + + override fun lift(value: Pointer): FfiGroupMetadata { + return FfiGroupMetadata(value) + } + + override fun read(buf: ByteBuffer): FfiGroupMetadata { + // 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: FfiGroupMetadata) = 8 + + override fun write(value: FfiGroupMetadata, 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))) + } +} + + + + public interface FfiStreamCloserInterface { fun `end`() @@ -2547,6 +2710,7 @@ sealed class GenericException(message: String): Exception(message) { class ApiException(message: String) : GenericException(message) class GroupException(message: String) : GenericException(message) class Signature(message: String) : GenericException(message) + class GroupMetadata(message: String) : GenericException(message) class Generic(message: String) : GenericException(message) @@ -2565,7 +2729,8 @@ public object FfiConverterTypeGenericError : FfiConverterRustBuffer GenericException.ApiException(FfiConverterString.read(buf)) 5 -> GenericException.GroupException(FfiConverterString.read(buf)) 6 -> GenericException.Signature(FfiConverterString.read(buf)) - 7 -> GenericException.Generic(FfiConverterString.read(buf)) + 7 -> GenericException.GroupMetadata(FfiConverterString.read(buf)) + 8 -> GenericException.Generic(FfiConverterString.read(buf)) else -> throw RuntimeException("invalid error enum value, something is very wrong!!") } @@ -2601,10 +2766,14 @@ public object FfiConverterTypeGenericError : FfiConverterRustBuffer { + is GenericException.GroupMetadata -> { buf.putInt(7) Unit } + is GenericException.Generic -> { + buf.putInt(8) + Unit + } }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } } diff --git a/library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so b/library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so index 08348c7b..e1ef2e40 100755 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 db076f80..76a714b7 100755 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 8cb4fda3..25c3f0ac 100755 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 90d6feb5..23cdf582 100755 Binary files a/library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so and b/library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so differ