From 0d2f13a663cf57c7aede80324ab7d1a9b414de29 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Thu, 24 Oct 2024 18:12:06 -0700 Subject: [PATCH] a few tweaks to android code --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 86 +++++---- src/index.ts | 179 +++++++++++++++--- src/lib/ConversationContainer.ts | 8 + 3 files changed, 215 insertions(+), 58 deletions(-) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 0298d713..a600baeb 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -1531,16 +1531,27 @@ class XMTPModule : Module() { logV("processWelcomeMessage") val client = clients[inboxId] ?: throw XMTPException("No client") + val group = + client.conversations.fromWelcome(Base64.decode(encryptedMessage, NO_WRAP)) + GroupWrapper.encode(client, group) + } + } + + AsyncFunction("processConversationWelcomeMessage") Coroutine { inboxId: String, encryptedMessage: String -> + withContext(Dispatchers.IO) { + logV("processConversationWelcomeMessage") + val client = clients[inboxId] ?: throw XMTPException("No client") + val conversation = client.conversations.conversationFromWelcome(Base64.decode(encryptedMessage, NO_WRAP)) ConversationContainerWrapper.encode(client, conversation) } } - Function("subscribeToV2Conversations") { inboxId: String -> + Function("subscribeToConversations") { inboxId: String -> // V2 ONLY logV("subscribeToConversations") - subscribeToV2Conversations(inboxId = inboxId) + subscribeToConversations(inboxId = inboxId) } Function("subscribeToGroups") { inboxId: String -> @@ -1751,7 +1762,7 @@ class XMTPModule : Module() { } } - AsyncFunction("v2ConversationConsentState") Coroutine { inboxId: String, conversationTopic: String -> + AsyncFunction("conversationConsentState") Coroutine { inboxId: String, conversationTopic: String -> withContext(Dispatchers.IO) { val conversation = findConversation(inboxId, conversationTopic) ?: throw XMTPException("no conversation found for $conversationTopic") @@ -1759,7 +1770,7 @@ class XMTPModule : Module() { } } - AsyncFunction("conversationConsentState") Coroutine { inboxId: String, groupId: String -> + AsyncFunction("conversationV3ConsentState") Coroutine { inboxId: String, groupId: String -> withContext(Dispatchers.IO) { val group = findGroup(inboxId, groupId) ?: throw XMTPException("no group found for $groupId") @@ -1844,20 +1855,20 @@ class XMTPModule : Module() { } } - Function("subscribeToConversations") { inboxId: String -> - logV("subscribeToConversations") - subscribeToGroups(inboxId = inboxId) + Function("subscribeToV3Conversations") { inboxId: String -> + logV("subscribeToV3Conversations") + subscribeToV3Conversations(inboxId = inboxId) } - Function("subscribeToAllConversationMessages") { inboxId: String, includeGroups: Boolean -> + Function("subscribeToAllConversationMessages") { inboxId: String -> logV("subscribeToAllConversationMessages") - subscribeToAllMessages(inboxId = inboxId, includeGroups = includeGroups) + subscribeToAllConversationMessages(inboxId = inboxId) } - AsyncFunction("subscribeToDmMessages") Coroutine { inboxId: String, id: String -> + AsyncFunction("subscribeToConversationMessages") Coroutine { inboxId: String, id: String -> withContext(Dispatchers.IO) { - logV("subscribeToDmMessages") - subscribeToGroupMessages( + logV("subscribeToConversationMessages") + subscribeToConversationMessages( inboxId = inboxId, id = id ) @@ -1866,20 +1877,20 @@ class XMTPModule : Module() { Function("unsubscribeFromAllConversationMessages") { inboxId: String -> logV("unsubscribeFromAllConversationMessages") - subscriptions[getGroupMessagesKey(inboxId)]?.cancel() + subscriptions[getConversationMessagesKey(inboxId)]?.cancel() } - Function("unsubscribeFromConversations") { inboxId: String -> - logV("unsubscribeFromConversations") - subscriptions[getGroupMessagesKey(inboxId)]?.cancel() + Function("unsubscribeFromV3Conversations") { inboxId: String -> + logV("unsubscribeFromV3Conversations") + subscriptions[getV3ConversationsKey(inboxId)]?.cancel() } - AsyncFunction("unsubscribeFromDmMessages") Coroutine { inboxId: String, topic: String -> + AsyncFunction("unsubscribeFromConversationMessages") Coroutine { inboxId: String, id: String -> withContext(Dispatchers.IO) { - logV("unsubscribeFromDmMessages") - unsubscribeFromMessages( + logV("unsubscribeFromConversationMessages") + unsubscribeFromConversationMessages( inboxId = inboxId, - topic = topic + id = id ) } } @@ -1955,11 +1966,11 @@ class XMTPModule : Module() { return null } - private fun subscribeToV2Conversations(inboxId: String) { + private fun subscribeToConversations(inboxId: String) { val client = clients[inboxId] ?: throw XMTPException("No client") - subscriptions[getV2ConversationsKey(inboxId)]?.cancel() - subscriptions[getV2ConversationsKey(inboxId)] = CoroutineScope(Dispatchers.IO).launch { + subscriptions[getConversationsKey(inboxId)]?.cancel() + subscriptions[getConversationsKey(inboxId)] = CoroutineScope(Dispatchers.IO).launch { try { client.conversations.stream().collect { conversation -> run { @@ -1980,7 +1991,7 @@ class XMTPModule : Module() { } } catch (e: Exception) { Log.e("XMTPModule", "Error in conversations subscription: $e") - subscriptions[getV2ConversationsKey(inboxId)]?.cancel() + subscriptions[getConversationsKey(inboxId)]?.cancel() } } } @@ -2007,11 +2018,11 @@ class XMTPModule : Module() { } } - private fun subscribeToConversations(inboxId: String) { + private fun subscribeToV3Conversations(inboxId: String) { val client = clients[inboxId] ?: throw XMTPException("No client") - subscriptions[getConversationsKey(client.inboxId)]?.cancel() - subscriptions[getConversationsKey(client.inboxId)] = CoroutineScope(Dispatchers.IO).launch { + subscriptions[getV3ConversationsKey(client.inboxId)]?.cancel() + subscriptions[getV3ConversationsKey(client.inboxId)] = CoroutineScope(Dispatchers.IO).launch { try { client.conversations.streamConversations().collect { conversation -> sendEvent( @@ -2024,7 +2035,7 @@ class XMTPModule : Module() { } } catch (e: Exception) { Log.e("XMTPModule", "Error in group subscription: $e") - subscriptions[getConversationsKey(client.inboxId)]?.cancel() + subscriptions[getV3ConversationsKey(client.inboxId)]?.cancel() } } } @@ -2176,7 +2187,7 @@ class XMTPModule : Module() { } } - private suspend fun subscribeToDmMessages(inboxId: String, id: String) { + private suspend fun subscribeToConversationMessages(inboxId: String, id: String) { val client = clients[inboxId] ?: throw XMTPException("No client") val conversation = client.findConversation(id) ?: throw XMTPException("no conversation found for $id") @@ -2214,11 +2225,11 @@ class XMTPModule : Module() { } private fun getConversationsKey(inboxId: String): String { - return "conversationsV3:$inboxId" + return "conversations:$inboxId" } - private fun getV2ConversationsKey(inboxId: String): String { - return "conversations:$inboxId" + private fun getV3ConversationsKey(inboxId: String): String { + return "conversationsV3:$inboxId" } private fun getGroupsKey(inboxId: String): String { @@ -2237,7 +2248,7 @@ class XMTPModule : Module() { subscriptions[conversation.cacheKey(inboxId)]?.cancel() } - private suspend fun unsubscribeFromGroupMessages( + private fun unsubscribeFromGroupMessages( inboxId: String, id: String, ) { @@ -2251,6 +2262,15 @@ class XMTPModule : Module() { subscriptions[group.cacheKey(inboxId)]?.cancel() } + private fun unsubscribeFromConversationMessages( + inboxId: String, + id: String, + ) { + val client = clients[inboxId] ?: throw XMTPException("No client") + val convo = client.findConversation(id) ?: return + subscriptions[convo.cacheKey(inboxId)]?.cancel() + } + private fun logV(msg: String) { if (isDebugEnabled) { Log.v("XMTPModule", msg) diff --git a/src/index.ts b/src/index.ts index 73177c58..715f1de0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,6 +28,7 @@ import { DefaultContentTypes } from './lib/types/DefaultContentType' import { ConversationOrder, GroupOptions } from './lib/types/GroupOptions' import { PermissionPolicySet } from './lib/types/PermissionPolicySet' import { getAddress } from './utils/address' +import { Dm } from './lib/Dm' export * from './context' export * from './hooks' @@ -122,7 +123,10 @@ export async function receiveSignature(requestID: string, signature: string) { return await XMTPModule.receiveSignature(requestID, signature) } -export async function receiveSCWSignature(requestID: string, signature: string) { +export async function receiveSCWSignature( + requestID: string, + signature: string +) { return await XMTPModule.receiveSCWSignature(requestID, signature) } @@ -285,6 +289,21 @@ export async function dropClient(inboxId: string) { return await XMTPModule.dropClient(inboxId) } +export async function findOrCreateDm< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>( + client: Client, + peerAddress: string, +): Promise> { + const dm = JSON.parse( + await XMTPModule.findOrCreateDm(client.inboxId, peerAddress) + ) + const members = dm['members']?.map((mem: string) => { + return Member.from(mem) + }) + return new Dm(client, dm, members) +} + export async function createGroup< ContentTypes extends DefaultContentTypes = DefaultContentTypes, >( @@ -375,39 +394,77 @@ export async function listGroups< }) } +export async function listV3Conversations< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>( + client: Client, + opts?: GroupOptions | undefined, + order?: ConversationOrder | undefined, + limit?: number | undefined +): Promise[]> { + return ( + await XMTPModule.listV3Conversations( + client.inboxId, + JSON.stringify(opts), + order, + limit + ) + ).map((json: string) => { + const jsonObj = JSON.parse(json) + const members = jsonObj.members.map((mem: string) => { + return Member.from(mem) + }) + if (jsonObj.version === ConversationVersion.GROUP) { + return new Group(client, jsonObj, members) + } else { + return new Dm(client, jsonObj, members) + } + }) +} + export async function listMemberInboxIds< ContentTypes extends DefaultContentTypes = DefaultContentTypes, >(client: Client, id: string): Promise { return XMTPModule.listMemberInboxIds(client.inboxId, id) } -export async function listGroupMembers( +export async function listPeerInboxId< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>(client: Client, dmId: string): Promise { + return XMTPModule.listPeerInboxId(client.inboxId, dmId) +} + +export async function listConversationMembers( inboxId: string, id: string ): Promise { - const members = await XMTPModule.listGroupMembers(inboxId, id) + const members = await XMTPModule.listConversationMembers(inboxId, id) return members.map((json: string) => { return Member.from(json) }) } -export async function prepareGroupMessage( +export async function prepareConversationMessage( inboxId: string, - groupId: string, + conversationId: string, content: any ): Promise { const contentJson = JSON.stringify(content) - return await XMTPModule.prepareGroupMessage(inboxId, groupId, contentJson) + return await XMTPModule.prepareConversationMessage(inboxId, conversationId, contentJson) } -export async function sendMessageToGroup( +export async function sendMessageToConversation( inboxId: string, - groupId: string, + conversationId: string, content: any ): Promise { const contentJson = JSON.stringify(content) - return await XMTPModule.sendMessageToGroup(inboxId, groupId, contentJson) + return await XMTPModule.sendMessageToConversation( + inboxId, + conversationId, + contentJson + ) } export async function publishPreparedGroupMessages( @@ -417,28 +474,26 @@ export async function publishPreparedGroupMessages( return await XMTPModule.publishPreparedGroupMessages(inboxId, groupId) } -export async function groupMessages< +export async function conversationMessages< ContentTypes extends DefaultContentTypes = DefaultContentTypes, >( client: Client, - id: string, + conversationId: string, limit?: number | undefined, before?: number | Date | undefined, after?: number | Date | undefined, direction?: | 'SORT_DIRECTION_ASCENDING' | 'SORT_DIRECTION_DESCENDING' - | undefined, - deliveryStatus?: MessageDeliveryStatus | undefined + | undefined ): Promise[]> { - const messages = await XMTPModule.groupMessages( + const messages = await XMTPModule.conversationMessages( client.inboxId, - id, + conversationId, limit, before, after, - direction, - deliveryStatus + direction ) return messages.map((json: string) => { return DecodedMessage.from(json, client) @@ -459,6 +514,58 @@ export async function findGroup< return new Group(client, group, members) } +export async function findConversation< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>( + client: Client, + conversationId: string +): Promise | undefined> { + const json = await XMTPModule.findConversation(client.inboxId, conversationId) + const conversation = JSON.parse(json) + const members = conversation['members']?.map((mem: string) => { + return Member.from(mem) + }) + + if (conversation.version === ConversationVersion.GROUP) { + return new Group(client, conversation, members) + } else { + return new Dm(client, conversation, members) + } +} + +export async function findConversationByTopic< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>( + client: Client, + topic: string +): Promise | undefined> { + const json = await XMTPModule.findConversationByTopic(client.inboxId, topic) + const conversation = JSON.parse(json) + const members = conversation['members']?.map((mem: string) => { + return Member.from(mem) + }) + + if (conversation.version === ConversationVersion.GROUP) { + return new Group(client, conversation, members) + } else { + return new Dm(client, conversation, members) + } +} + +export async function findDm< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>( + client: Client, + address: string +): Promise | undefined> { + const json = await XMTPModule.findDm(client.inboxId, address) + const dm = JSON.parse(json) + const members = dm['members']?.map((mem: string) => { + return Member.from(mem) + }) + return new Dm(client, dm, members) +} + export async function findV3Message< ContentTypes extends DefaultContentTypes = DefaultContentTypes, >( @@ -469,16 +576,16 @@ export async function findV3Message< return DecodedMessage.from(message, client) } -export async function syncGroups(inboxId: string) { - await XMTPModule.syncGroups(inboxId) +export async function syncConversations(inboxId: string) { + await XMTPModule.syncConversations(inboxId) } -export async function syncAllGroups(inboxId: string): Promise { - return await XMTPModule.syncAllGroups(inboxId) +export async function syncAllConversations(inboxId: string): Promise { + return await XMTPModule.syncAllConversations(inboxId) } -export async function syncGroup(inboxId: string, id: string) { - await XMTPModule.syncGroup(inboxId, id) +export async function syncConversation(inboxId: string, id: string) { + await XMTPModule.syncConversation(inboxId, id) } export async function subscribeToGroupMessages(inboxId: string, id: string) { @@ -1282,14 +1389,14 @@ export async function isInboxDenied( return XMTPModule.isInboxDenied(clientInboxId, inboxId) } -export async function processGroupMessage< +export async function processConversationMessage< ContentTypes extends DefaultContentTypes = DefaultContentTypes, >( client: Client, id: string, encryptedMessage: string ): Promise> { - const json = XMTPModule.processGroupMessage( + const json = XMTPModule.processConversationMessage( client.inboxId, id, encryptedMessage @@ -1314,6 +1421,28 @@ export async function processWelcomeMessage< return new Group(client, group, members) } +export async function processConversationWelcomeMessage< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>( + client: Client, + encryptedMessage: string +): Promise>> { + const json = await XMTPModule.processConversationWelcomeMessage( + client.inboxId, + encryptedMessage + ) + const conversation = JSON.parse(json) + const members = conversation['members']?.map((mem: string) => { + return Member.from(mem) + }) + + if (conversation.version === ConversationVersion.GROUP) { + return new Group(client, conversation, members) + } else { + return new Dm(client, conversation, members) + } +} + export async function exportNativeLogs() { return XMTPModule.exportNativeLogs() } diff --git a/src/lib/ConversationContainer.ts b/src/lib/ConversationContainer.ts index c461027e..e8a5b4a9 100644 --- a/src/lib/ConversationContainer.ts +++ b/src/lib/ConversationContainer.ts @@ -20,3 +20,11 @@ export interface ConversationContainer< state: ConsentState lastMessage?: DecodedMessage } + +export interface ConversationFunctions< + ContentTypes extends DefaultContentTypes, +> { + sendMessage(content: string): Promise; + loadMessages(limit?: number): Promise[]>; + updateState(state: ConsentState): void; +} \ No newline at end of file