From fe274c273052ac98bf185771ed886bb83a33d996 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Wed, 13 Nov 2024 10:14:14 -0800 Subject: [PATCH 1/4] add inbox state and find by inbox id --- android/build.gradle | 2 +- example/ios/Podfile.lock | 8 +++---- ios/XMTPModule.swift | 45 +++++++++++++++++++++++++++++++------ ios/XMTPReactNative.podspec | 2 +- src/index.ts | 26 +++++++++++++++++++++ src/lib/Client.ts | 13 ++++++++++- src/lib/Conversations.ts | 14 +++++++++++- 7 files changed, 95 insertions(+), 15 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index abd8db8f2..7fcbe5e5b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -98,7 +98,7 @@ repositories { dependencies { implementation project(':expo-modules-core') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" - implementation "org.xmtp:android:3.0.3" + implementation "org.xmtp:android:3.0.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" diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 689075f0a..a24051143 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -449,7 +449,7 @@ PODS: - GenericJSON (~> 2.0) - Logging (~> 1.0.0) - secp256k1.swift (~> 0.1) - - XMTP (3.0.3): + - XMTP (3.0.4): - Connect-Swift (= 0.12.0) - GzipSwift - LibXMTP (= 3.0.0) @@ -458,7 +458,7 @@ PODS: - ExpoModulesCore - MessagePacker - secp256k1.swift - - XMTP (= 3.0.3) + - XMTP (= 3.0.4) - Yoga (1.14.0) DEPENDENCIES: @@ -763,8 +763,8 @@ SPEC CHECKSUMS: secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634 SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1 web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959 - XMTP: ab14d9456330823f1c52b08ce5281d1e20c03f7f - XMTPReactNative: f7c9dc2eadef5c7d9590d2b7764ab805d16c7a5e + XMTP: dba23b4f3bcee464ca2f7569e1dc05fd9f4c0148 + XMTPReactNative: 117d8a00b063044029c11f50320a6b9c8abcdea7 Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9 PODFILE CHECKSUM: 0e6fe50018f34e575d38dc6a1fdf1f99c9596cdd diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index 191a81a93..2d13073d8 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -173,6 +173,18 @@ public class XMTPModule: Module { return try InboxStateWrapper.encode(inboxState) } + AsyncFunction("getInboxStates") { + (inboxId: String, refreshFromNetwork: Bool, inboxIds: [String]) + -> [String] in + guard let client = await clientsManager.getClient(key: inboxId) + else { + throw Error.noClient + } + let inboxStates = try await client.inboxStatesForInboxIds( + refreshFromNetwork: refreshFromNetwork, inboxIds: inboxIds) + return try inboxStates.map { InboxStateWrapper.encode($0) } + } + Function("preAuthenticateToInboxCallbackCompleted") { DispatchQueue.global().async { self.preAuthenticateToInboxCallbackDeferred?.signal() @@ -545,13 +557,26 @@ public class XMTPModule: Module { } } + AsyncFunction("findDmByInboxId") { + (inboxId: String, peerInboxId: String) -> String? in + guard let client = await clientsManager.getClient(key: inboxId) + else { + throw Error.noClient + } + if let dm = try client.findDmByInboxId(inboxId: peerInboxId) { + return try await DmWrapper.encode(dm, client: client) + } else { + return nil + } + } + AsyncFunction("findDmByAddress") { (inboxId: String, peerAddress: String) -> String? in guard let client = await clientsManager.getClient(key: inboxId) else { throw Error.noClient } - if let dm = try await client.findDm(address: peerAddress) { + if let dm = try await client.findDmByAddress(address: peerAddress) { return try await DmWrapper.encode(dm, client: client) } else { return nil @@ -1001,7 +1026,8 @@ public class XMTPModule: Module { AsyncFunction("isAdmin") { (clientInboxId: String, id: String, inboxId: String) -> Bool in - guard let client = await clientsManager.getClient(key: clientInboxId) + guard + let client = await clientsManager.getClient(key: clientInboxId) else { throw Error.noClient } @@ -1014,7 +1040,8 @@ public class XMTPModule: Module { AsyncFunction("isSuperAdmin") { (clientInboxId: String, id: String, inboxId: String) -> Bool in - guard let client = await clientsManager.getClient(key: clientInboxId) + guard + let client = await clientsManager.getClient(key: clientInboxId) else { throw Error.noClient } @@ -1053,7 +1080,8 @@ public class XMTPModule: Module { AsyncFunction("addAdmin") { (clientInboxId: String, id: String, inboxId: String) in - guard let client = await clientsManager.getClient(key: clientInboxId) + guard + let client = await clientsManager.getClient(key: clientInboxId) else { throw Error.noClient } @@ -1066,7 +1094,8 @@ public class XMTPModule: Module { AsyncFunction("addSuperAdmin") { (clientInboxId: String, id: String, inboxId: String) in - guard let client = await clientsManager.getClient(key: clientInboxId) + guard + let client = await clientsManager.getClient(key: clientInboxId) else { throw Error.noClient } @@ -1079,7 +1108,8 @@ public class XMTPModule: Module { AsyncFunction("removeAdmin") { (clientInboxId: String, id: String, inboxId: String) in - guard let client = await clientsManager.getClient(key: clientInboxId) + guard + let client = await clientsManager.getClient(key: clientInboxId) else { throw Error.noClient } @@ -1092,7 +1122,8 @@ public class XMTPModule: Module { AsyncFunction("removeSuperAdmin") { (clientInboxId: String, id: String, inboxId: String) in - guard let client = await clientsManager.getClient(key: clientInboxId) + guard + let client = await clientsManager.getClient(key: clientInboxId) else { throw Error.noClient } diff --git a/ios/XMTPReactNative.podspec b/ios/XMTPReactNative.podspec index e95482c73..9bf755fcb 100644 --- a/ios/XMTPReactNative.podspec +++ b/ios/XMTPReactNative.podspec @@ -26,5 +26,5 @@ Pod::Spec.new do |s| s.source_files = "**/*.{h,m,swift}" s.dependency 'secp256k1.swift' s.dependency "MessagePacker" - s.dependency "XMTP", "= 3.0.3" + s.dependency "XMTP", "= 3.0.4" end diff --git a/src/index.ts b/src/index.ts index 7f30cad80..1fae6317b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -85,6 +85,17 @@ export async function getInboxState( return InboxState.from(inboxState) } +export async function getInboxStates( + inboxId: InboxId, + refreshFromNetwork: boolean, + inboxIds: InboxId[], +): Promise { + const inboxStates = await XMTPModule.getInboxStates(inboxId, refreshFromNetwork, inboxIds) + return inboxStates.map((json: string) => { + return InboxState.from(json) + }) +} + export function preAuthenticateToInboxCallbackCompleted() { XMTPModule.preAuthenticateToInboxCallbackCompleted() } @@ -374,6 +385,21 @@ export async function findConversationByTopic< } } +export async function findDmByInboxId< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>( + client: Client, + peerInboxId: InboxId +): Promise | undefined> { + const json = await XMTPModule.findDmByInboxId(client.inboxId, peerInboxId) + const dm = JSON.parse(json) + if (!dm || Object.keys(dm).length === 0) { + return undefined + } + + return new Dm(client, dm) +} + export async function findDmByAddress< ContentTypes extends DefaultContentTypes = DefaultContentTypes, >( diff --git a/src/lib/Client.ts b/src/lib/Client.ts index b296d7b2f..6f9ed29e7 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -393,7 +393,7 @@ export class Client< } /** - * Make a request for a inboxs state. + * Make a request for your inbox state. * * @param {boolean} refreshFromNetwork - If you want to refresh the current state of in the inbox from the network or not. * @returns {Promise} A Promise resolving to a InboxState. @@ -402,6 +402,17 @@ export class Client< return await XMTPModule.getInboxState(this.inboxId, refreshFromNetwork) } + /** + * Make a request for a list of inbox states. + * + * @param {InboxId[]} inboxIds - The inboxIds to get the associate inbox states for. + * @param {boolean} refreshFromNetwork - If you want to refresh the current state the inbox from the network or not. + * @returns {Promise} A Promise resolving to a list of InboxState. + */ + async inboxStates(refreshFromNetwork: boolean, inboxIds: InboxId[]): Promise { + return await XMTPModule.getInboxStates(this.inboxId, refreshFromNetwork, inboxIds) + } + /** * Determines whether the current user can send messages to the specified peers over groups. * diff --git a/src/lib/Conversations.ts b/src/lib/Conversations.ts index 75509c6e7..e585c124d 100644 --- a/src/lib/Conversations.ts +++ b/src/lib/Conversations.ts @@ -1,4 +1,4 @@ -import { Client } from './Client' +import { Client, InboxId } from './Client' import { ConversationVersion } from './Conversation' import { DecodedMessage } from './DecodedMessage' import { Dm, DmParams } from './Dm' @@ -105,6 +105,18 @@ export default class Conversations< return await XMTPModule.findGroup(this.client, groupId) } + /** + * This method returns a Dm by the inboxId if that dm exists in the local database. + * To get the latest list of dms from the network, call sync() first. + * + * @returns {Promise} A Promise that resolves to a Dm or undefined if not found. + */ + async findDmByInboxId( + inboxId: InboxId + ): Promise | undefined> { + return await XMTPModule.findDmByInboxId(this.client, inboxId) + } + /** * This method returns a Dm by the address if that dm exists in the local database. * To get the latest list of dms from the network, call sync() first. From 9471bd18b21e54fcd6504c549703766eb4602aba Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Wed, 13 Nov 2024 10:16:13 -0800 Subject: [PATCH 2/4] implement it on android --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index b7479673d..35ed22f0f 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -274,6 +274,14 @@ class XMTPModule : Module() { } } + AsyncFunction("getInboxStates") Coroutine { inboxId: String, refreshFromNetwork: Boolean, inboxIds: List -> + withContext(Dispatchers.IO) { + val client = clients[inboxId] ?: throw XMTPException("No client") + val inboxStates = client.inboxStatesForInboxIds(refreshFromNetwork, inboxIds) + inboxStates.map { InboxStateWrapper.encode(it) } + } + } + Function("preAuthenticateToInboxCallbackCompleted") { logV("preAuthenticateToInboxCallbackCompleted") preAuthenticateToInboxCallbackDeferred?.complete(Unit) @@ -531,11 +539,22 @@ class XMTPModule : Module() { } } + AsyncFunction("findDmByInboxId") Coroutine { inboxId: String, peerInboxId: String -> + withContext(Dispatchers.IO) { + logV("findDmByInboxId") + val client = clients[inboxId] ?: throw XMTPException("No client") + val dm = client.findDmByInboxId(peerInboxId) + dm?.let { + DmWrapper.encode(client, dm) + } + } + } + AsyncFunction("findDmByAddress") Coroutine { inboxId: String, peerAddress: String -> withContext(Dispatchers.IO) { logV("findDmByAddress") val client = clients[inboxId] ?: throw XMTPException("No client") - val dm = client.findDm(peerAddress) + val dm = client.findDmByAddress(peerAddress) dm?.let { DmWrapper.encode(client, dm) } From 3c0c0a3f90bb284673b62c98fec8eb2fa13415b2 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Wed, 13 Nov 2024 10:20:45 -0800 Subject: [PATCH 3/4] fix: add tests --- example/src/tests/clientTests.ts | 16 ++++++++++++++++ example/src/tests/conversationTests.ts | 15 +++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/example/src/tests/clientTests.ts b/example/src/tests/clientTests.ts index f1ea53e8f..d916ef779 100644 --- a/example/src/tests/clientTests.ts +++ b/example/src/tests/clientTests.ts @@ -281,3 +281,19 @@ test('production client creation does not error', async () => { } return true }) + +test('can find others inbox states', async () => { + const [alix, bo, caro] = await createClients(3) + + const states = await alix.inboxStates(true, [bo.inboxId, caro.inboxId]) + assert( + states[0].recoveryAddress.toLowerCase === bo.address.toLowerCase, + `addresses dont match ${states[0].recoveryAddress} and ${bo.address}` + ) + assert( + states[1].addresses[0].toLowerCase === caro.address.toLowerCase, + `clients dont match ${states[1].addresses[0]} and ${caro.address}` + ) + + return true +}) diff --git a/example/src/tests/conversationTests.ts b/example/src/tests/conversationTests.ts index ebba986d1..6c2c5cf65 100644 --- a/example/src/tests/conversationTests.ts +++ b/example/src/tests/conversationTests.ts @@ -67,6 +67,21 @@ test('can find a conversation by topic', async () => { return true }) +test('can find a dm by inbox id', async () => { + const [alixClient, boClient] = await createClients(2) + const alixDm = await alixClient.conversations.findOrCreateDm(boClient.address) + + await boClient.conversations.sync() + const boDm = await boClient.conversations.findDmByInboxId(alixClient.inboxId) + + assert( + boDm?.id === alixDm.id, + `bo dm id ${boDm?.id} does not match alix dm id ${alixDm.id}` + ) + + return true +}) + test('can find a dm by address', async () => { const [alixClient, boClient] = await createClients(2) const alixDm = await alixClient.conversations.findOrCreateDm(boClient.address) From 19afe06a4f46d39db4d0d1b3911edca6a404f6f7 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Wed, 13 Nov 2024 10:30:56 -0800 Subject: [PATCH 4/4] missing try --- ios/XMTPModule.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index 2d13073d8..94f6910a9 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -182,7 +182,7 @@ public class XMTPModule: Module { } let inboxStates = try await client.inboxStatesForInboxIds( refreshFromNetwork: refreshFromNetwork, inboxIds: inboxIds) - return try inboxStates.map { InboxStateWrapper.encode($0) } + return try inboxStates.map { try InboxStateWrapper.encode($0) } } Function("preAuthenticateToInboxCallbackCompleted") {