diff --git a/android/build.gradle b/android/build.gradle index 43c5ec20a..f8c506495 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:0.14.13" + implementation "org.xmtp:android:0.14.14" 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/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 0f3c61fd2..496277323 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -448,6 +448,18 @@ class XMTPModule : Module() { } } + AsyncFunction("getOrCreateInboxId") Coroutine { address: String, environment: String -> + withContext(Dispatchers.IO) { + try { + logV("getOrCreateInboxId") + val options = ClientOptions(api = apiEnvironments(environment, null)) + Client.getOrCreateInboxId(options = options, address = address) + } catch (e: Exception) { + throw XMTPException("Failed to getOrCreateInboxId: ${e.message}") + } + } + } + AsyncFunction("encryptAttachment") { inboxId: String, fileJson: String -> logV("encryptAttachment") val client = clients[inboxId] ?: throw XMTPException("No client") diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ClientWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ClientWrapper.kt index cb9f2e958..9cbe85ea8 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ClientWrapper.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ClientWrapper.kt @@ -9,7 +9,8 @@ class ClientWrapper { return mapOf( "inboxId" to client.inboxId, "address" to client.address, - "installationId" to client.installationId + "installationId" to client.installationId, + "dbPath" to client.dbPath ) } diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/GroupWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/GroupWrapper.kt index deafbc0b6..4529ef761 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/GroupWrapper.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/GroupWrapper.kt @@ -18,6 +18,10 @@ class GroupWrapper { "topic" to group.topic, "creatorInboxId" to group.creatorInboxId(), "isActive" to group.isActive(), + "name" to group.name, + "imageUrlSquare" to group.imageUrlSquare, + "description" to group.description, + "pinnedFrameUrl" to group.pinnedFrameUrl ) } diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index d380a7047..3ea1add7d 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 (0.13.13): + - XMTP (0.13.14): - Connect-Swift (= 0.12.0) - GzipSwift - LibXMTP (= 0.5.6-beta4) @@ -458,7 +458,7 @@ PODS: - ExpoModulesCore - MessagePacker - secp256k1.swift - - XMTP (= 0.13.13) + - XMTP (= 0.13.14) - Yoga (1.14.0) DEPENDENCIES: @@ -763,8 +763,8 @@ SPEC CHECKSUMS: secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634 SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1 web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959 - XMTP: bce55f0cd8f4481b8f460581ddf7808dcd09c1de - XMTPReactNative: 30b80235f99ee434ad9567a4d64d6cb505afe1a8 + XMTP: f590939a897f00a0f957223eba31d5d6bee10a48 + XMTPReactNative: ca2cdef4fcadb3e072fec62de63f379c84464250 Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9 PODFILE CHECKSUM: 95d6ace79946933ecf80684613842ee553dd76a2 diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index 0bca66bf2..60d3e55fb 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -31,13 +31,21 @@ test('can make a MLS V3 client', async () => { 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, ]) - await Client.createRandom({ + const client = await Client.createRandom({ env: 'local', appVersion: 'Testing/0.0.0', enableV3: true, dbEncryptionKey: keyBytes, }) + const inboxId = await Client.getOrCreateInboxId(client.address, { + env: 'local', + }) + + assert( + client.inboxId === inboxId, + `inboxIds should match but were ${client.inboxId} and ${inboxId}` + ) return true }) @@ -53,6 +61,10 @@ test('can delete a local database', async () => { }` ) + assert( + client.dbPath !== '', + `client dbPath should be set but was ${client.dbPath}` + ) await client.deleteLocalDatabase() client = await Client.createRandom({ env: 'local', @@ -912,6 +924,56 @@ test('can stream groups', async () => { return true }) +test('can list groups', async () => { + const [alixClient, boClient] = await createClients(2) + + const group1 = await boClient.conversations.newGroup([alixClient.address], { + name: 'group1 name', + imageUrlSquare: 'www.group1image.com', + }) + const group2 = await boClient.conversations.newGroup([alixClient.address], { + name: 'group2 name', + imageUrlSquare: 'www.group2image.com', + }) + + const boGroups = await boClient.conversations.listGroups() + await alixClient.conversations.syncGroups() + const alixGroups = await alixClient.conversations.listGroups() + + assert( + boGroups.length === alixGroups.length, + `group lengths should be the same but bo was ${boGroups.length} and alix was ${alixGroups.length}` + ) + + const boGroup1 = await boClient.conversations.findGroup(group1.id) + const boGroup2 = await boClient.conversations.findGroup(group2.id) + + const alixGroup1 = await alixClient.conversations.findGroup(group1.id) + const alixGroup2 = await alixClient.conversations.findGroup(group2.id) + + assert( + boGroup2?.name === 'group2 name', + `Group 2 name for bo should be group2 name but was ${boGroup2?.name}` + ) + + assert( + boGroup1?.imageUrlSquare === 'www.group1image.com', + `Group 2 url for bo should be www.group1image.com but was ${boGroup1?.imageUrlSquare}` + ) + + assert( + alixGroup1?.name === 'group1 name', + `Group 1 name for alix should be group1 name but was ${alixGroup1?.name}` + ) + + assert( + alixGroup2?.imageUrlSquare === 'www.group2image.com', + `Group 2 url for alix should be www.group2image.com but was ${alixGroup2?.imageUrlSquare}` + ) + + return true +}) + test('can list all groups and conversations', async () => { const [alixClient, boClient, caroClient] = await createClients(3) diff --git a/ios/Wrappers/ClientWrapper.swift b/ios/Wrappers/ClientWrapper.swift index 13a262ad5..b4cb7784a 100644 --- a/ios/Wrappers/ClientWrapper.swift +++ b/ios/Wrappers/ClientWrapper.swift @@ -15,6 +15,7 @@ struct ClientWrapper { "inboxId": client.inboxID, "address": client.address, "installationId": client.installationID, + "dbPath": client.dbPath, ] } diff --git a/ios/Wrappers/GroupWrapper.swift b/ios/Wrappers/GroupWrapper.swift index 80144dcc0..71906faf2 100644 --- a/ios/Wrappers/GroupWrapper.swift +++ b/ios/Wrappers/GroupWrapper.swift @@ -20,6 +20,10 @@ struct GroupWrapper { "topic": group.topic, "creatorInboxId": try group.creatorInboxId(), "isActive": try group.isActive(), + "name": try group.groupName(), + "imageUrlSquare": try group.groupImageUrlSquare(), + "description": try group.groupDescription(), + "pinnedFrameUrl": try group.groupPinnedFrameUrl() ] } diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index a34af7ff2..93523c7c9 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -292,6 +292,15 @@ public class XMTPModule: Module { throw Error.noClient } } + + AsyncFunction("getOrCreateInboxId") { (address: String, environment: String) -> String in + do { + let options = createClientConfig(env: environment, appVersion: nil) + return try await XMTP.Client.getOrCreateInboxId(options: options, address: address) + } catch { + throw Error.noClient + } + } AsyncFunction("encryptAttachment") { (inboxId: String, fileJson: String) -> String in guard let client = await clientsManager.getClient(key: inboxId) else { diff --git a/ios/XMTPReactNative.podspec b/ios/XMTPReactNative.podspec index f05ebcfb5..bb034d47d 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", "= 0.13.13" + s.dependency "XMTP", "= 0.13.14" end diff --git a/src/index.ts b/src/index.ts index 564421978..3d129ff3a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -513,6 +513,13 @@ export async function staticCanMessage( ) } +export async function getOrCreateInboxId( + address: string, + environment: 'local' | 'dev' | 'production' +): Promise { + return await XMTPModule.getOrCreateInboxId(getAddress(address), environment) +} + export async function encryptAttachment( inboxId: string, file: DecryptedLocalAttachment diff --git a/src/lib/Client.ts b/src/lib/Client.ts index 7dd46c66f..aac08d70a 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -33,6 +33,7 @@ export class Client< address: string inboxId: InboxId installationId: string + dbPath: string conversations: Conversations contacts: Contacts codecRegistry: { [key: string]: XMTPModule.ContentCodec } @@ -104,6 +105,7 @@ export class Client< inboxId: string address: string installationId: string + dbPath: string }) => { this.removeSubscription(enableSubscription) this.removeSubscription(createSubscription) @@ -114,6 +116,7 @@ export class Client< message.address, message.inboxId as InboxId, message.installationId, + message.dbPath, options.codecs || [] ) ) @@ -185,6 +188,7 @@ export class Client< client['address'], client['inboxId'], client['installationId'], + client['dbPath'], options?.codecs || [] ) } @@ -226,6 +230,7 @@ export class Client< client['address'], client['inboxId'], client['installationId'], + client['dbPath'], options.codecs || [] ) } @@ -303,15 +308,32 @@ export class Client< return { enableSubscription, createSubscription } } + /** + * Static method to determine the inboxId for the address. + * + * @param {string} peerAddress - The address of the peer to check for messaging eligibility. + * @param {Partial} opts - Optional configuration options for the Client. + * @returns {Promise} + */ + static async getOrCreateInboxId( + address: string, + opts?: Partial + ): Promise { + const options = defaultOptions(opts) + return await XMTPModule.getOrCreateInboxId(address, options.env) + } + constructor( address: string, inboxId: InboxId, installationId: string, + dbPath: string, codecs: XMTPModule.ContentCodec[] = [] ) { this.address = address this.inboxId = inboxId this.installationId = installationId + this.dbPath = dbPath this.conversations = new Conversations(this) this.contacts = new Contacts(this) this.codecRegistry = {} diff --git a/src/lib/Group.ts b/src/lib/Group.ts index 237bf5fa1..3be634d22 100644 --- a/src/lib/Group.ts +++ b/src/lib/Group.ts @@ -29,6 +29,8 @@ export class Group< name: string isGroupActive: boolean imageUrlSquare: string + description: string + pinnedFrameUrl: string constructor( client: XMTP.Client, @@ -41,6 +43,8 @@ export class Group< name: string isGroupActive: boolean imageUrlSquare: string + description: string + pinnedFrameUrl: string } ) { this.client = client @@ -52,6 +56,8 @@ export class Group< this.name = params.name this.isGroupActive = params.isGroupActive this.imageUrlSquare = params.imageUrlSquare + this.description = params.description + this.pinnedFrameUrl = params.pinnedFrameUrl } /**