diff --git a/.eslintrc.js b/.eslintrc.js index e42a86b98..c4b0b9ab3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,17 +1,9 @@ module.exports = { root: true, - parser: "@typescript-eslint/parser", - parserOptions: { - project: ["./tsconfig.json", "./example/tsconfig.json"] - }, - plugins: ["@typescript-eslint"], extends: ['universe/native', 'universe/web'], ignorePatterns: ['build'], plugins: ['prettier'], globals: { __dirname: true, }, - rules: { - "@typescript-eslint/no-floating-promises": ["error"], - }, } diff --git a/.github/workflows/tsc.yml b/.github/workflows/tsc.yml deleted file mode 100644 index 5d64a66f5..000000000 --- a/.github/workflows/tsc.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Typescript -on: - pull_request: -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: actions/setup-node@v3 - - run: yarn - - run: yarn tsc diff --git a/.gitignore b/.gitignore index b9ec20389..84068a05d 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,3 @@ android/keystores/debug.keystore # Typedocs docs/ -**/.yarn/* \ No newline at end of file diff --git a/README.md b/README.md index 53977260a..6ff2146ce 100644 --- a/README.md +++ b/README.md @@ -437,7 +437,3 @@ The `env` parameter accepts one of three valid values: `dev`, `production`, or ` - `local`: Use to have a client communicate with an XMTP node you are running locally. For example, an XMTP node developer can set `env` to `local` to generate client traffic to test a node running locally. The `production` network is configured to store messages indefinitely. XMTP may occasionally delete messages and keys from the `dev` network, and will provide advance notice in the [XMTP Discord community](https://discord.gg/xmtp). - -## Enabling group chat - -Coming soon... \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index d5276128e..d3218cc35 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -98,20 +98,8 @@ repositories { dependencies { implementation project(':expo-modules-core') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" - implementation "org.xmtp:android:0.10.0" + implementation "org.xmtp:android:0.10.4" implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.facebook.react:react-native:0.71.3' implementation "com.daveanthonythomas.moshipack:moshipack:1.0.1" - implementation "org.xmtp:android:0.10.0" - // xmtp-android local testing setup below (comment org.xmtp:android above) - // implementation files('/xmtp-android/library/build/outputs/aar/library-debug.aar') - // implementation 'com.google.crypto.tink:tink-android:1.7.0' - // implementation 'io.grpc:grpc-kotlin-stub:1.3.0' - // implementation 'io.grpc:grpc-okhttp:1.51.1' - // implementation 'io.grpc:grpc-protobuf-lite:1.51.0' - // implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' - // implementation 'org.web3j:crypto:5.0.0' - // implementation "net.java.dev.jna:jna:5.13.0@aar" - // implementation 'com.google.protobuf:protobuf-kotlin-lite:3.22.3' - // implementation 'org.xmtp:proto-kotlin:3.40.1' } diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 856716b28..cf53ec4a4 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -1,6 +1,5 @@ package expo.modules.xmtpreactnativesdk -import android.content.Context import android.net.Uri import android.util.Base64 import android.util.Base64.NO_WRAP @@ -8,7 +7,6 @@ import android.util.Log import androidx.core.net.toUri import com.google.gson.JsonParser import com.google.protobuf.kotlin.toByteString -import expo.modules.kotlin.exception.Exceptions import expo.modules.kotlin.functions.Coroutine import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition @@ -19,8 +17,6 @@ import expo.modules.xmtpreactnativesdk.wrappers.ConversationWrapper import expo.modules.xmtpreactnativesdk.wrappers.DecodedMessageWrapper import expo.modules.xmtpreactnativesdk.wrappers.DecryptedLocalAttachment import expo.modules.xmtpreactnativesdk.wrappers.EncryptedLocalAttachment -import expo.modules.xmtpreactnativesdk.wrappers.GroupWrapper -import expo.modules.xmtpreactnativesdk.wrappers.ConversationContainerWrapper import expo.modules.xmtpreactnativesdk.wrappers.PreparedLocalMessage import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope @@ -34,7 +30,6 @@ import org.json.JSONObject import org.xmtp.android.library.Client import org.xmtp.android.library.ClientOptions import org.xmtp.android.library.Conversation -import org.xmtp.android.library.Group import org.xmtp.android.library.PreEventCallback import org.xmtp.android.library.PreparedMessage import org.xmtp.android.library.SendOptions @@ -54,20 +49,15 @@ import org.xmtp.android.library.messages.PrivateKeyBuilder import org.xmtp.android.library.messages.Signature import org.xmtp.android.library.messages.getPublicKeyBundle import org.xmtp.android.library.push.XMTPPush -import org.xmtp.android.library.toHex import org.xmtp.proto.keystore.api.v1.Keystore.TopicMap.TopicData import org.xmtp.proto.message.api.v1.MessageApiOuterClass import org.xmtp.proto.message.contents.PrivateKeyOuterClass -import uniffi.xmtpv3.GroupPermissions import java.io.File import java.util.Date import java.util.UUID import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException -import com.facebook.common.util.Hex -import org.xmtp.android.library.messages.Topic -import org.xmtp.android.library.push.Service class ReactNativeSigner(var module: XMTPModule, override var address: String) : SigningKey { private val continuations: MutableMap> = mutableMapOf() @@ -111,15 +101,7 @@ fun Conversation.cacheKey(clientAddress: String): String { return "${clientAddress}:${topic}" } -fun Group.cacheKey(clientAddress: String): String { - return "${clientAddress}:${id}" -} - class XMTPModule : Module() { - - val context: Context - get() = appContext.reactContext ?: throw Exceptions.ReactContextLost() - private fun apiEnvironments(env: String, appVersion: String?): ClientOptions.Api { return when (env) { "local" -> ClientOptions.Api( @@ -147,7 +129,6 @@ class XMTPModule : Module() { private var signer: ReactNativeSigner? = null private val isDebugEnabled = BuildConfig.DEBUG // TODO: consider making this configurable private val conversations: MutableMap = mutableMapOf() - private val groups: MutableMap = mutableMapOf() private val subscriptions: MutableMap = mutableMapOf() private var preEnableIdentityCallbackDeferred: CompletableDeferred? = null private var preCreateIdentityCallbackDeferred: CompletableDeferred? = null @@ -156,22 +137,12 @@ class XMTPModule : Module() { override fun definition() = ModuleDefinition { Name("XMTP") Events( - // Auth "sign", "authed", - "preCreateIdentityCallback", - "preEnableIdentityCallback", - // Conversations "conversation", - "group", - "conversationContainer", "message", - "allGroupMessage", - // Conversation - "conversationMessage", - // Group - "groupMessage" - + "preEnableIdentityCallback", + "preCreateIdentityCallback" ) Function("address") { clientAddress: String -> @@ -180,17 +151,11 @@ class XMTPModule : Module() { client?.address ?: "No Client." } - AsyncFunction("deleteLocalDatabase") { clientAddress: String -> - val client = clients[clientAddress] ?: throw XMTPException("No client") - client.deleteLocalDatabase() - } - // // Auth functions // - AsyncFunction("auth") { address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, enableAlphaMls: Boolean?, dbEncryptionKey: List?, dbPath: String? -> + AsyncFunction("auth") { address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean? -> logV("auth") - requireNotProductionEnvForAlphaMLS(enableAlphaMls, environment) val reactSigner = ReactNativeSigner(module = this@XMTPModule, address = address) signer = reactSigner @@ -202,20 +167,10 @@ class XMTPModule : Module() { preCreateIdentityCallback.takeIf { hasCreateIdentityCallback == true } val preEnableIdentityCallback: PreEventCallback? = preEnableIdentityCallback.takeIf { hasEnableIdentityCallback == true } - val context = if (enableAlphaMls == true) context else null - val encryptionKeyBytes = - dbEncryptionKey?.foldIndexed(ByteArray(dbEncryptionKey.size)) { i, a, v -> - a.apply { set(i, v.toByte()) } - } - val options = ClientOptions( api = apiEnvironments(environment, appVersion), preCreateIdentityCallback = preCreateIdentityCallback, - preEnableIdentityCallback = preEnableIdentityCallback, - enableAlphaMls = enableAlphaMls == true, - appContext = context, - dbEncryptionKey = encryptionKeyBytes, - dbPath = dbPath + preEnableIdentityCallback = preEnableIdentityCallback ) clients[address] = Client().create(account = reactSigner, options = options) ContentJson.Companion @@ -229,9 +184,8 @@ class XMTPModule : Module() { } // Generate a random wallet and set the client to that - AsyncFunction("createRandom") { environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, enableAlphaMls: Boolean?, dbEncryptionKey: List?, dbPath: String? -> + AsyncFunction("createRandom") { environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean? -> logV("createRandom") - requireNotProductionEnvForAlphaMLS(enableAlphaMls, environment) val privateKey = PrivateKeyBuilder() if (hasCreateIdentityCallback == true) @@ -242,20 +196,11 @@ class XMTPModule : Module() { preCreateIdentityCallback.takeIf { hasCreateIdentityCallback == true } val preEnableIdentityCallback: PreEventCallback? = preEnableIdentityCallback.takeIf { hasEnableIdentityCallback == true } - val context = if (enableAlphaMls == true) context else null - val encryptionKeyBytes = - dbEncryptionKey?.foldIndexed(ByteArray(dbEncryptionKey.size)) { i, a, v -> - a.apply { set(i, v.toByte()) } - } val options = ClientOptions( api = apiEnvironments(environment, appVersion), preCreateIdentityCallback = preCreateIdentityCallback, - preEnableIdentityCallback = preEnableIdentityCallback, - enableAlphaMls = enableAlphaMls == true, - appContext = context, - dbEncryptionKey = encryptionKeyBytes, - dbPath = dbPath + preEnableIdentityCallback = preEnableIdentityCallback ) val randomClient = Client().create(account = privateKey, options = options) ContentJson.Companion @@ -263,23 +208,10 @@ class XMTPModule : Module() { randomClient.address } - AsyncFunction("createFromKeyBundle") { keyBundle: String, environment: String, appVersion: String?, enableAlphaMls: Boolean?, dbEncryptionKey: List?, dbPath: String? -> - logV("createFromKeyBundle") - requireNotProductionEnvForAlphaMLS(enableAlphaMls, environment) - + AsyncFunction("createFromKeyBundle") { keyBundle: String, environment: String, appVersion: String? -> try { - val context = if (enableAlphaMls == true) context else null - val encryptionKeyBytes = - dbEncryptionKey?.foldIndexed(ByteArray(dbEncryptionKey.size)) { i, a, v -> - a.apply { set(i, v.toByte()) } - } - val options = ClientOptions( - api = apiEnvironments(environment, appVersion), - enableAlphaMls = enableAlphaMls == true, - appContext = context, - dbEncryptionKey = encryptionKeyBytes, - dbPath = dbPath - ) + logV("createFromKeyBundle") + val options = ClientOptions(api = apiEnvironments(environment, appVersion)) val bundle = PrivateKeyOuterClass.PrivateKeyBundle.parseFrom( Base64.decode( @@ -373,12 +305,6 @@ class XMTPModule : Module() { client.canMessage(peerAddress) } - AsyncFunction("canGroupMessage") { clientAddress: String, peerAddresses: List -> - logV("canGroupMessage") - val client = clients[clientAddress] ?: throw XMTPException("No client") - client.canMessageV3(peerAddresses) - } - AsyncFunction("staticCanMessage") { peerAddress: String, environment: String, appVersion: String? -> try { logV("staticCanMessage") @@ -481,29 +407,6 @@ class XMTPModule : Module() { } } - AsyncFunction("listGroups") Coroutine { clientAddress: String -> - withContext(Dispatchers.IO) { - logV("listGroups") - val client = clients[clientAddress] ?: throw XMTPException("No client") - val groupList = client.conversations.listGroups() - groupList.map { group -> - groups[group.cacheKey(clientAddress)] = group - GroupWrapper.encode(client, group) - } - } - } - - AsyncFunction("listAll") Coroutine { clientAddress: String -> - withContext(Dispatchers.IO) { - val client = clients[clientAddress] ?: throw XMTPException("No client") - val conversationContainerList = client.conversations.list(includeGroups = true) - conversationContainerList.map { conversation -> - conversations[conversation.cacheKey(clientAddress)] = conversation - ConversationContainerWrapper.encode(client, conversation) - } - } - } - AsyncFunction("loadMessages") Coroutine { clientAddress: String, topic: String, limit: Int?, before: Long?, after: Long?, direction: String? -> withContext(Dispatchers.IO) { logV("loadMessages") @@ -527,24 +430,6 @@ class XMTPModule : Module() { } } - AsyncFunction("groupMessages") Coroutine { clientAddress: String, id: String, limit: Int?, before: Long?, after: Long?, direction: String? -> - withContext(Dispatchers.IO) { - logV("groupMessages") - val client = clients[clientAddress] ?: throw XMTPException("No client") - val beforeDate = if (before != null) Date(before) else null - val afterDate = if (after != null) Date(after) else null - val group = findGroup(clientAddress, id) - group?.decryptedMessages( - limit = limit, - before = beforeDate, - after = afterDate, - direction = MessageApiOuterClass.SortDirection.valueOf( - direction ?: "SORT_DIRECTION_DESCENDING" - ) - )?.map { DecodedMessageWrapper.encode(it) } - } - } - AsyncFunction("loadBatchMessages") Coroutine { clientAddress: String, topics: List -> withContext(Dispatchers.IO) { logV("loadBatchMessages") @@ -609,23 +494,6 @@ class XMTPModule : Module() { } } - AsyncFunction("sendMessageToGroup") Coroutine { clientAddress: String, id: String, contentJson: String -> - withContext(Dispatchers.IO) { - logV("sendMessageToGroup") - val group = - findGroup( - clientAddress = clientAddress, - id = id - ) - ?: throw XMTPException("no group found for $id") - val sending = ContentJson.fromJson(contentJson) - group.send( - content = sending.content, - options = SendOptions(contentType = sending.type) - ) - } - } - AsyncFunction("prepareMessage") Coroutine { clientAddress: String, conversationTopic: String, contentJson: String -> withContext(Dispatchers.IO) { logV("prepareMessage") @@ -735,131 +603,15 @@ class XMTPModule : Module() { ConversationWrapper.encode(client, conversation) } } - AsyncFunction("createGroup") Coroutine { clientAddress: String, peerAddresses: List, permission: String -> - withContext(Dispatchers.IO) { - logV("createGroup") - val client = clients[clientAddress] ?: throw XMTPException("No client") - val permissionLevel = when (permission) { - "creator_admin" -> GroupPermissions.GROUP_CREATOR_IS_ADMIN - else -> GroupPermissions.EVERYONE_IS_ADMIN - } - val group = client.conversations.newGroup(peerAddresses, permissionLevel) - GroupWrapper.encode(client, group) - } - } - - AsyncFunction("listMemberAddresses") Coroutine { clientAddress: String, groupId: String -> - withContext(Dispatchers.IO) { - logV("listMembers") - val client = clients[clientAddress] ?: throw XMTPException("No client") - val group = findGroup(clientAddress, groupId) - group?.memberAddresses() - } - } - - AsyncFunction("syncGroups") Coroutine { clientAddress: String -> - withContext(Dispatchers.IO) { - logV("syncGroups") - val client = clients[clientAddress] ?: throw XMTPException("No client") - client.conversations.syncGroups() - } - } - - AsyncFunction("syncGroup") Coroutine { clientAddress: String, id: String -> - withContext(Dispatchers.IO) { - logV("syncGroup") - val client = clients[clientAddress] ?: throw XMTPException("No client") - val group = findGroup(clientAddress, id) - group?.sync() - } - } - - AsyncFunction("addGroupMembers") Coroutine { clientAddress: String, id: String, peerAddresses: List -> - withContext(Dispatchers.IO) { - logV("addGroupMembers") - val client = clients[clientAddress] ?: throw XMTPException("No client") - val group = findGroup(clientAddress, id) - - group?.addMembers(peerAddresses) - } - } - - AsyncFunction("removeGroupMembers") Coroutine { clientAddress: String, id: String, peerAddresses: List -> - withContext(Dispatchers.IO) { - logV("removeGroupMembers") - val client = clients[clientAddress] ?: throw XMTPException("No client") - val group = findGroup(clientAddress, id) - - group?.removeMembers(peerAddresses) - } - } - - AsyncFunction("isGroupActive") Coroutine { clientAddress: String, id: String -> - withContext(Dispatchers.IO) { - logV("isGroupActive") - val client = clients[clientAddress] ?: throw XMTPException("No client") - val group = findGroup(clientAddress, id) - - group?.isActive() - } - } - - AsyncFunction("isGroupAdmin") Coroutine { clientAddress: String, id: String -> - withContext(Dispatchers.IO) { - logV("isGroupAdmin") - val client = clients[clientAddress] ?: throw XMTPException("No client") - val group = findGroup(clientAddress, id) - - group?.isAdmin() - } - } - - AsyncFunction("processGroupMessage") Coroutine { clientAddress: String, id: String, encryptedMessage: String -> - withContext(Dispatchers.IO) { - logV("processGroupMessage") - val client = clients[clientAddress] ?: throw XMTPException("No client") - val group = findGroup(clientAddress, id) - - val message = group?.processMessage(Base64.decode(encryptedMessage, NO_WRAP)) - ?: throw XMTPException("could not decrypt message for $id") - DecodedMessageWrapper.encodeMap(message.decrypt()) - } - } - - AsyncFunction("processWelcomeMessage") Coroutine { clientAddress: String, encryptedMessage: String -> - withContext(Dispatchers.IO) { - logV("processWelcomeMessage") - val client = clients[clientAddress] ?: throw XMTPException("No client") - - val group = - client.conversations.fromWelcome(Base64.decode(encryptedMessage, NO_WRAP)) - GroupWrapper.encode(client, group) - } - } Function("subscribeToConversations") { clientAddress: String -> logV("subscribeToConversations") subscribeToConversations(clientAddress = clientAddress) } - Function("subscribeToGroups") { clientAddress: String -> - logV("subscribeToGroups") - subscribeToGroups(clientAddress = clientAddress) - } - - Function("subscribeToAll") { clientAddress: String -> - logV("subscribeToAll") - subscribeToAll(clientAddress = clientAddress) - } - - Function("subscribeToAllMessages") { clientAddress: String, includeGroups: Boolean -> + Function("subscribeToAllMessages") { clientAddress: String -> logV("subscribeToAllMessages") - subscribeToAllMessages(clientAddress = clientAddress, includeGroups = includeGroups) - } - - Function("subscribeToAllGroupMessages") { clientAddress: String -> - logV("subscribeToAllGroupMessages") - subscribeToAllGroupMessages(clientAddress = clientAddress) + subscribeToAllMessages(clientAddress = clientAddress) } AsyncFunction("subscribeToMessages") Coroutine { clientAddress: String, topic: String -> @@ -872,36 +624,16 @@ class XMTPModule : Module() { } } - AsyncFunction("subscribeToGroupMessages") Coroutine { clientAddress: String, id: String -> - withContext(Dispatchers.IO) { - logV("subscribeToGroupMessages") - subscribeToGroupMessages( - clientAddress = clientAddress, - id = id - ) - } - } - Function("unsubscribeFromConversations") { clientAddress: String -> logV("unsubscribeFromConversations") subscriptions[getConversationsKey(clientAddress)]?.cancel() } - Function("unsubscribeFromGroups") { clientAddress: String -> - logV("unsubscribeFromGroups") - subscriptions[getGroupsKey(clientAddress)]?.cancel() - } - Function("unsubscribeFromAllMessages") { clientAddress: String -> logV("unsubscribeFromAllMessages") subscriptions[getMessagesKey(clientAddress)]?.cancel() } - Function("unsubscribeFromAllGroupMessages") { clientAddress: String -> - logV("unsubscribeFromAllGroupMessages") - subscriptions[getGroupMessagesKey(clientAddress)]?.cancel() - } - AsyncFunction("unsubscribeFromMessages") Coroutine { clientAddress: String, topic: String -> withContext(Dispatchers.IO) { logV("unsubscribeFromMessages") @@ -912,50 +644,19 @@ class XMTPModule : Module() { } } - AsyncFunction("unsubscribeFromGroupMessages") Coroutine { clientAddress: String, id: String -> - withContext(Dispatchers.IO) { - logV("unsubscribeFromGroupMessages") - unsubscribeFromGroupMessages( - clientAddress = clientAddress, - id = id - ) - } - } - Function("registerPushToken") { pushServer: String, token: String -> logV("registerPushToken") xmtpPush = XMTPPush(appContext.reactContext!!, pushServer) xmtpPush?.register(token) } - Function("subscribePushTopics") { clientAddress: String, topics: List -> + Function("subscribePushTopics") { topics: List -> logV("subscribePushTopics") if (topics.isNotEmpty()) { if (xmtpPush == null) { throw XMTPException("Push server not registered") } - val client = clients[clientAddress] ?: throw XMTPException("No client") - - val hmacKeysResult = client.conversations.getHmacKeys() - val subscriptions = topics.map { - val hmacKeys = hmacKeysResult.hmacKeysMap - val result = hmacKeys[it]?.valuesList?.map { hmacKey -> - Service.Subscription.HmacKey.newBuilder().also { sub_key -> - sub_key.key = hmacKey.hmacKey - sub_key.thirtyDayPeriodsSinceEpoch = hmacKey.thirtyDayPeriodsSinceEpoch - }.build() - } - - Service.Subscription.newBuilder().also { sub -> - sub.addAllHmacKeys(result) - if (!result.isNullOrEmpty()) { - sub.addAllHmacKeys(result) - } - sub.topic = it - }.build() - } - - xmtpPush?.subscribeWithMetadata(subscriptions) + xmtpPush?.subscribe(topics) } } @@ -1032,32 +733,6 @@ class XMTPModule : Module() { logV("preEnableIdentityCallbackCompleted") preEnableIdentityCallbackDeferred?.complete(Unit) } - - AsyncFunction("allowGroups") Coroutine { clientAddress: String, groupIds: List -> - logV("allowGroups") - val client = clients[clientAddress] ?: throw XMTPException("No client") - val groupDataIds = groupIds.mapNotNull { Hex.hexStringToByteArray(it) } - client.contacts.allowGroup(groupDataIds) - } - - AsyncFunction("denyGroups") Coroutine { clientAddress: String, groupIds: List -> - logV("denyGroups") - val client = clients[clientAddress] ?: throw XMTPException("No client") - val groupDataIds = groupIds.mapNotNull { Hex.hexStringToByteArray(it) } - client.contacts.denyGroup(groupDataIds) - } - - AsyncFunction("isGroupAllowed") { clientAddress: String, groupId: String -> - logV("isGroupAllowed") - val client = clients[clientAddress] ?: throw XMTPException("No client") - client.contacts.isGroupAllowed(Hex.hexStringToByteArray(groupId)) - } - - AsyncFunction("isGroupDenied") { clientAddress: String, groupId: String -> - logV("isGroupDenied") - val client = clients[clientAddress] ?: throw XMTPException("No client") - client.contacts.isGroupDenied(Hex.hexStringToByteArray(groupId)) - } } // @@ -1085,27 +760,6 @@ class XMTPModule : Module() { return null } - private suspend fun findGroup( - clientAddress: String, - id: String, - ): Group? { - val client = clients[clientAddress] ?: throw XMTPException("No client") - - val cacheKey = "${clientAddress}:${id}" - val cacheGroup = groups[cacheKey] - if (cacheGroup != null) { - return cacheGroup - } else { - val group = client.conversations.listGroups() - .firstOrNull { it.id.toHex() == id } - if (group != null) { - groups[group.cacheKey(clientAddress)] = group - return group - } - } - return null - } - private fun subscribeToConversations(clientAddress: String) { val client = clients[clientAddress] ?: throw XMTPException("No client") @@ -1136,85 +790,15 @@ class XMTPModule : Module() { } } - private fun subscribeToGroups(clientAddress: String) { - val client = clients[clientAddress] ?: throw XMTPException("No client") - - subscriptions[getGroupsKey(clientAddress)]?.cancel() - subscriptions[getGroupsKey(clientAddress)] = CoroutineScope(Dispatchers.IO).launch { - try { - client.conversations.streamGroups().collect { group -> - sendEvent( - "group", - mapOf( - "clientAddress" to clientAddress, - "group" to GroupWrapper.encodeToObj(client, group) - ) - ) - } - } catch (e: Exception) { - Log.e("XMTPModule", "Error in group subscription: $e") - subscriptions[getGroupsKey(clientAddress)]?.cancel() - } - } - } - - private fun subscribeToAll(clientAddress: String) { - val client = clients[clientAddress] ?: throw XMTPException("No client") - - subscriptions[getConversationsKey(clientAddress)]?.cancel() - subscriptions[getConversationsKey(clientAddress)] = CoroutineScope(Dispatchers.IO).launch { - try { - client.conversations.streamAll().collect { conversation -> - sendEvent( - "conversationContainer", - mapOf( - "clientAddress" to clientAddress, - "conversationContainer" to ConversationContainerWrapper.encodeToObj( - client, - conversation - ) - ) - ) - } - } catch (e: Exception) { - Log.e("XMTPModule", "Error in subscription to groups + conversations: $e") - subscriptions[getConversationsKey(clientAddress)]?.cancel() - } - } - } - - private fun subscribeToAllMessages(clientAddress: String, includeGroups: Boolean = false) { + private fun subscribeToAllMessages(clientAddress: String) { val client = clients[clientAddress] ?: throw XMTPException("No client") subscriptions[getMessagesKey(clientAddress)]?.cancel() subscriptions[getMessagesKey(clientAddress)] = CoroutineScope(Dispatchers.IO).launch { try { - client.conversations.streamAllDecryptedMessages(includeGroups = includeGroups) - .collect { message -> - sendEvent( - "message", - mapOf( - "clientAddress" to clientAddress, - "message" to DecodedMessageWrapper.encodeMap(message), - ) - ) - } - } catch (e: Exception) { - Log.e("XMTPModule", "Error in all messages subscription: $e") - subscriptions[getMessagesKey(clientAddress)]?.cancel() - } - } - } - - private fun subscribeToAllGroupMessages(clientAddress: String) { - val client = clients[clientAddress] ?: throw XMTPException("No client") - - subscriptions[getGroupMessagesKey(clientAddress)]?.cancel() - subscriptions[getGroupMessagesKey(clientAddress)] = CoroutineScope(Dispatchers.IO).launch { - try { - client.conversations.streamAllGroupDecryptedMessages().collect { message -> + client.conversations.streamAllDecryptedMessages().collect { message -> sendEvent( - "allGroupMessage", + "message", mapOf( "clientAddress" to clientAddress, "message" to DecodedMessageWrapper.encodeMap(message), @@ -1222,8 +806,8 @@ class XMTPModule : Module() { ) } } catch (e: Exception) { - Log.e("XMTPModule", "Error in all group messages subscription: $e") - subscriptions[getGroupMessagesKey(clientAddress)]?.cancel() + Log.e("XMTPModule", "Error in all messages subscription: $e") + subscriptions[getMessagesKey(clientAddress)]?.cancel() } } } @@ -1240,11 +824,10 @@ class XMTPModule : Module() { try { conversation.streamDecryptedMessages().collect { message -> sendEvent( - "conversationMessage", + "message", mapOf( "clientAddress" to clientAddress, "message" to DecodedMessageWrapper.encodeMap(message), - "topic" to topic, ) ) } @@ -1255,49 +838,14 @@ class XMTPModule : Module() { } } - private suspend fun subscribeToGroupMessages(clientAddress: String, id: String) { - val group = - findGroup( - clientAddress = clientAddress, - id = id - ) ?: return - subscriptions[group.cacheKey(clientAddress)]?.cancel() - subscriptions[group.cacheKey(clientAddress)] = - CoroutineScope(Dispatchers.IO).launch { - try { - group.streamDecryptedMessages().collect { message -> - sendEvent( - "groupMessage", - mapOf( - "clientAddress" to clientAddress, - "message" to DecodedMessageWrapper.encodeMap(message), - "groupId" to id, - ) - ) - } - } catch (e: Exception) { - Log.e("XMTPModule", "Error in messages subscription: $e") - subscriptions[group.cacheKey(clientAddress)]?.cancel() - } - } - } - private fun getMessagesKey(clientAddress: String): String { return "messages:$clientAddress" } - private fun getGroupMessagesKey(clientAddress: String): String { - return "groupMessages:$clientAddress" - } - private fun getConversationsKey(clientAddress: String): String { return "conversations:$clientAddress" } - private fun getGroupsKey(clientAddress: String): String { - return "groups:$clientAddress" - } - private suspend fun unsubscribeFromMessages( clientAddress: String, topic: String, @@ -1310,18 +858,6 @@ class XMTPModule : Module() { subscriptions[conversation.cacheKey(clientAddress)]?.cancel() } - private suspend fun unsubscribeFromGroupMessages( - clientAddress: String, - id: String, - ) { - val conversation = - findGroup( - clientAddress = clientAddress, - id = id - ) ?: return - subscriptions[conversation.cacheKey(clientAddress)]?.cancel() - } - private fun logV(msg: String) { if (isDebugEnabled) { Log.v("XMTPModule", msg) @@ -1339,12 +875,6 @@ class XMTPModule : Module() { preCreateIdentityCallbackDeferred?.await() preCreateIdentityCallbackDeferred = null } - - private fun requireNotProductionEnvForAlphaMLS(enableAlphaMls: Boolean?, environment: String) { - if (enableAlphaMls == true && (environment == "production")) { - throw XMTPException("Environment must be \"local\" or \"dev\" to enable alpha MLS") - } - } } diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ContentJson.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ContentJson.kt index ece84a14b..b46015697 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ContentJson.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ContentJson.kt @@ -30,9 +30,6 @@ import org.xmtp.android.library.codecs.description import org.xmtp.android.library.codecs.getReactionAction import org.xmtp.android.library.codecs.getReactionSchema import org.xmtp.android.library.codecs.id -import uniffi.xmtpv3.org.xmtp.android.library.codecs.ContentTypeGroupMembershipChange -import uniffi.xmtpv3.org.xmtp.android.library.codecs.GroupMembershipChangeCodec -import uniffi.xmtpv3.org.xmtp.android.library.codecs.GroupMembershipChanges import java.net.URL class ContentJson( @@ -54,7 +51,6 @@ class ContentJson( Client.register(RemoteAttachmentCodec()) Client.register(ReplyCodec()) Client.register(ReadReceiptCodec()) - Client.register(GroupMembershipChangeCodec()) } fun fromJsonObject(obj: JsonObject): ContentJson { @@ -175,21 +171,6 @@ class ContentJson( "readReceipt" to "" ) - ContentTypeGroupMembershipChange.id -> mapOf( - "groupChange" to mapOf( - "membersAdded" to (content as GroupMembershipChanges).membersAddedList.map { - mapOf( - "address" to it.accountAddress, - "initiatedByAddress" to it.initiatedByAccountAddress - )}, - "membersRemoved" to content.membersRemovedList.map { - mapOf( - "address" to it.accountAddress, - "initiatedByAddress" to it.initiatedByAccountAddress - )}, - ) - ) - else -> { val json = JsonObject() encodedContent?.let { diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationContainerWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationContainerWrapper.kt deleted file mode 100644 index 1af77351b..000000000 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationContainerWrapper.kt +++ /dev/null @@ -1,29 +0,0 @@ -package expo.modules.xmtpreactnativesdk.wrappers - -import android.util.Base64 -import com.google.gson.GsonBuilder -import org.xmtp.android.library.Client -import org.xmtp.android.library.Conversation - -class ConversationContainerWrapper { - - companion object { - fun encodeToObj(client: Client, conversation: Conversation): Map { - when (conversation.version) { - Conversation.Version.GROUP -> { - val group = (conversation as Conversation.Group).group - return GroupWrapper.encodeToObj(client, group) - } - else -> { - return ConversationWrapper.encodeToObj(client, conversation) - } - } - } - - fun encode(client: Client, conversation: Conversation): String { - val gson = GsonBuilder().create() - val obj = ConversationContainerWrapper.encodeToObj(client, conversation) - return gson.toJson(obj) - } - } -} diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationWrapper.kt index efb0e3a01..6d31ccb8f 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationWrapper.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationWrapper.kt @@ -24,7 +24,7 @@ class ConversationWrapper { "context" to context, "topic" to conversation.topic, "peerAddress" to conversation.peerAddress, - "version" to "DIRECT", + "version" to if (conversation.version == Conversation.Version.V1) "v1" else "v2", "conversationID" to (conversation.conversationId ?: ""), "keyMaterial" to (conversation.keyMaterial?.let { Base64.encodeToString(it, Base64.NO_WRAP) } ?: "") ) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/GroupWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/GroupWrapper.kt deleted file mode 100644 index 0529773f0..000000000 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/GroupWrapper.kt +++ /dev/null @@ -1,38 +0,0 @@ -package expo.modules.xmtpreactnativesdk.wrappers - -import android.util.Base64 -import android.util.Base64.NO_WRAP -import com.google.gson.GsonBuilder -import org.xmtp.android.library.Client -import org.xmtp.android.library.Conversation -import org.xmtp.android.library.Group -import org.xmtp.android.library.toHex -import uniffi.xmtpv3.GroupPermissions - -class GroupWrapper { - - companion object { - fun encodeToObj(client: Client, group: Group): Map { - val permissionString = when (group.permissionLevel()) { - GroupPermissions.EVERYONE_IS_ADMIN -> "everyone_admin" - GroupPermissions.GROUP_CREATOR_IS_ADMIN -> "creator_admin" - } - return mapOf( - "clientAddress" to client.address, - "id" to group.id.toHex(), - "createdAt" to group.createdAt.time, - "peerAddresses" to Conversation.Group(group).peerAddresses, - "version" to "GROUP", - "topic" to group.id.toHex(), - "permissionLevel" to permissionString, - "adminAddress" to group.adminAddress() - ) - } - - fun encode(client: Client, group: Group): String { - val gson = GsonBuilder().create() - val obj = encodeToObj(client, group) - return gson.toJson(obj) - } - } -} \ No newline at end of file diff --git a/example/App.tsx b/example/App.tsx index f6c70c434..2a7f8b531 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -14,7 +14,6 @@ import { XmtpProvider } from 'xmtp-react-native-sdk' import ConversationCreateScreen from './src/ConversationCreateScreen' import ConversationScreen from './src/ConversationScreen' -import GroupScreen from './src/GroupScreen' import HomeScreen from './src/HomeScreen' import LaunchScreen from './src/LaunchScreen' import { Navigator } from './src/Navigation' @@ -91,11 +90,6 @@ export default function App() { options={{ title: 'Conversation' }} initialParams={{ topic: '' }} /> - 2.0) - Logging (~> 1.0.0) - secp256k1.swift (~> 0.1) - - XMTP (0.10.1): + - XMTP (0.10.3): - Connect-Swift (= 0.12.0) - GzipSwift - - LibXMTP (= 0.4.3-beta5) + - LibXMTP (= 0.4.4-beta2) - web3.swift - XMTPReactNative (0.1.0): - ExpoModulesCore - MessagePacker - secp256k1.swift - - XMTP (= 0.10.1) + - XMTP (= 0.10.3) - Yoga (1.14.0) DEPENDENCIES: @@ -509,7 +505,6 @@ DEPENDENCIES: - react-native-quick-crypto (from `../node_modules/react-native-quick-crypto`) - react-native-randombytes (from `../node_modules/react-native-randombytes`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - - react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`) - react-native-webview (from `../node_modules/react-native-webview`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) @@ -525,7 +520,6 @@ DEPENDENCIES: - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - - RNFS (from `../node_modules/react-native-fs`) - RNScreens (from `../node_modules/react-native-screens`) - RNSVG (from `../node_modules/react-native-svg`) - XMTPReactNative (from `../../ios`) @@ -640,8 +634,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-randombytes" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" - react-native-sqlite-storage: - :path: "../node_modules/react-native-sqlite-storage" react-native-webview: :path: "../node_modules/react-native-webview" React-perflogger: @@ -672,8 +664,6 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" RNCAsyncStorage: :path: "../node_modules/@react-native-async-storage/async-storage" - RNFS: - :path: "../node_modules/react-native-fs" RNScreens: :path: "../node_modules/react-native-screens" RNSVG: @@ -711,7 +701,7 @@ SPEC CHECKSUMS: GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - LibXMTP: 9580b03bff217acb4f173c08cd55cf5935d4f3f3 + LibXMTP: 0c073613451e3850bfcaaab5438b481fe887cd97 Logging: 9ef4ecb546ad3169398d5a723bc9bea1c46bef26 MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02 MMKV: ed58ad794b3f88c24d604a5b74f3fba17fcbaf74 @@ -741,7 +731,6 @@ SPEC CHECKSUMS: react-native-quick-crypto: 455c1b411db006dba1026a30681ececb19180187 react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846 react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc - react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261 react-native-webview: bdc091de8cf7f8397653e30182efcd9f772e03b3 React-perflogger: 4987ad83731c23d11813c84263963b0d3028c966 React-RCTActionSheet: 5ad952b2a9740d87a5bd77280c4bc23f6f89ea0c @@ -757,14 +746,13 @@ SPEC CHECKSUMS: React-runtimeexecutor: ffe826b7b1cfbc32a35ed5b64d5886c0ff75f501 ReactCommon: 7f3dd5e98a9ec627c6b03d26c062bf37ea9fc888 RNCAsyncStorage: 618d03a5f52fbccb3d7010076bc54712844c18ef - RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396 secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634 SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1 web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959 - XMTP: 28a385a7390d5df8f39f02c0615dc2ccb11bbbc9 - XMTPReactNative: a0a01706e94fd35763204cbd6f4d3a43c1a244df + XMTP: bf00ef58d4fbcc8ab740145a6303591adf3fb355 + XMTPReactNative: fae44562e5e457c66fde8e2613e1dfd3c1a71113 Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9 PODFILE CHECKSUM: 95d6ace79946933ecf80684613842ee553dd76a2 diff --git a/example/package.json b/example/package.json index 3391a849e..ed42a73b2 100644 --- a/example/package.json +++ b/example/package.json @@ -30,16 +30,13 @@ "react-native-config": "^1.5.1", "react-native-crypto": "^2.2.0", "react-native-encrypted-storage": "^4.0.3", - "react-native-fs": "^2.20.0", - "react-native-get-random-values": "^1.11.0", + "react-native-get-random-values": "^1.10.0", "react-native-mmkv": "^2.8.0", - "react-native-modal-selector": "^2.1.2", "react-native-quick-base64": "^2.0.8", "react-native-quick-crypto": "^0.6.1", "react-native-randombytes": "^3.6.1", "react-native-safe-area-context": "4.5.0", "react-native-screens": "~3.20.0", - "react-native-sqlite-storage": "^6.0.1", "react-native-svg": "^13.9.0", "react-native-url-polyfill": "^2.0.0", "react-native-webview": "^13.8.1", diff --git a/example/src/ConversationCreateScreen.tsx b/example/src/ConversationCreateScreen.tsx index 6973bb6ac..9f0676dc9 100644 --- a/example/src/ConversationCreateScreen.tsx +++ b/example/src/ConversationCreateScreen.tsx @@ -1,6 +1,6 @@ import { NativeStackScreenProps } from '@react-navigation/native-stack' import React, { useState } from 'react' -import { Button, ScrollView, Switch, Text, TextInput, View } from 'react-native' +import { Button, ScrollView, Text, TextInput } from 'react-native' import { useXmtp } from 'xmtp-react-native-sdk' import { NavigationParamList } from './Navigation' @@ -13,33 +13,19 @@ export default function ConversationCreateScreen({ const [alert, setAlert] = useState('') const [isCreating, setCreating] = useState(false) const { client } = useXmtp() - const [groupsEnabled, setGroupsEnabled] = useState(false) - const startNewConversation = async (toAddress: string) => { if (!client) { setAlert('Client not initialized') return } - if (groupsEnabled) { - const toAddresses = toAddress.split(',') - const canMessage = await client.canGroupMessage(toAddresses) - if (!canMessage) { - setAlert(`${toAddress} cannot be added to a group conversation yet`) - return - } - const group = await client.conversations.newGroup(toAddresses) - navigation.navigate('group', { id: group.id }) - } else { - const canMessage = await client.canMessage(toAddress) - if (!canMessage) { - setAlert(`${toAddress} is not on the XMTP network yet`) - return - } - const convo = await client.conversations.newConversation(toAddress) - navigation.navigate('conversation', { topic: convo.topic }) + const canMessage = await client.canMessage(toAddress) + if (!canMessage) { + setAlert(`${toAddress} is not on the XMTP network yet`) + return } + const convo = await client.conversations.newConversation(toAddress) + navigation.navigate('conversation', { topic: convo.topic }) } - return ( <> @@ -62,15 +48,6 @@ export default function ConversationCreateScreen({ opacity: isCreating ? 0.5 : 1, }} /> - - - setGroupsEnabled((previousState) => !previousState) - } - /> - Create Group: {groupsEnabled ? 'ON' : 'OFF'} -