From 5d7980f72a68245985d0b81756f251c8055bb5ec Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 24 Jun 2024 15:07:17 -0700 Subject: [PATCH 01/13] bump the android version --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index d8f2bb3b2..bb23d8911 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.13.7" + implementation "org.xmtp:android:0.13.8" 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" From 16be0357feb8c387e836e266f5b7104cf692ae82 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 24 Jun 2024 15:17:00 -0700 Subject: [PATCH 02/13] add bridge methods --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 1550d3235..58818d738 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -6,21 +6,24 @@ import android.util.Base64 import android.util.Base64.NO_WRAP import android.util.Log import androidx.core.net.toUri +import com.facebook.common.util.Hex 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 +import expo.modules.xmtpreactnativesdk.wrappers.ClientWrapper import expo.modules.xmtpreactnativesdk.wrappers.ConsentWrapper import expo.modules.xmtpreactnativesdk.wrappers.ConsentWrapper.Companion.consentStateToString import expo.modules.xmtpreactnativesdk.wrappers.ContentJson +import expo.modules.xmtpreactnativesdk.wrappers.ConversationContainerWrapper 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.MemberWrapper import expo.modules.xmtpreactnativesdk.wrappers.PreparedLocalMessage import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope @@ -49,10 +52,12 @@ import org.xmtp.android.library.codecs.RemoteAttachment import org.xmtp.android.library.codecs.decoded import org.xmtp.android.library.messages.EnvelopeBuilder import org.xmtp.android.library.messages.InvitationV1ContextBuilder +import org.xmtp.android.library.messages.MessageDeliveryStatus import org.xmtp.android.library.messages.Pagination 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.Service import org.xmtp.android.library.push.XMTPPush import org.xmtp.android.library.toHex import org.xmtp.proto.keystore.api.v1.Keystore.TopicMap.TopicData @@ -66,12 +71,6 @@ import java.util.UUID import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException -import com.facebook.common.util.Hex -import expo.modules.xmtpreactnativesdk.wrappers.ClientWrapper -import expo.modules.xmtpreactnativesdk.wrappers.MemberWrapper -import org.xmtp.android.library.messages.MessageDeliveryStatus -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() @@ -190,6 +189,14 @@ class XMTPModule : Module() { client?.inboxId ?: "No Client." } + AsyncFunction("findInboxIdFromAddress") Coroutine { inboxId: String, address: String -> + withContext(Dispatchers.IO) { + logV("findInboxIdFromAddress") + val client = clients[inboxId] ?: throw XMTPException("No client") + client.inboxIdFromAddress(address) + } + } + AsyncFunction("deleteLocalDatabase") { inboxId: String -> logV(inboxId) logV(clients.toString()) @@ -573,6 +580,22 @@ class XMTPModule : Module() { } } + AsyncFunction("findV3Message") Coroutine { inboxId: String, messageId: String -> + withContext(Dispatchers.IO) { + logV("findV3Message") + val client = clients[inboxId] ?: throw XMTPException("No client") + client.findMessage(Hex.hexStringToByteArray(messageId)) + } + } + + AsyncFunction("findGroup") Coroutine { inboxId: String, groupId: String -> + withContext(Dispatchers.IO) { + logV("findGroup") + val client = clients[inboxId] ?: throw XMTPException("No client") + client.findGroup(Hex.hexStringToByteArray(groupId)) + } + } + AsyncFunction("loadBatchMessages") Coroutine { inboxId: String, topics: List -> withContext(Dispatchers.IO) { logV("loadBatchMessages") From 82fa1a2dfbbfdb2b7a8e6b95cea88480b0386701 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 24 Jun 2024 15:20:38 -0700 Subject: [PATCH 03/13] add message history functionality --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 85 +++++++++++-------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 58818d738..6ba6f11be 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -216,42 +216,52 @@ class XMTPModule : Module() { } } + AsyncFunction("requestMessageHistorySync") Coroutine { inboxId: String -> + withContext(Dispatchers.IO) { + val client = clients[inboxId] ?: throw XMTPException("No client") + client.requestMessageHistorySync() + } + } + // // Auth functions // - AsyncFunction("auth") { address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, enableV3: Boolean?, dbEncryptionKey: List?, dbDirectory: String? -> - logV("auth") - val reactSigner = ReactNativeSigner(module = this@XMTPModule, address = address) - signer = reactSigner - - if (hasCreateIdentityCallback == true) - preCreateIdentityCallbackDeferred = CompletableDeferred() - if (hasEnableIdentityCallback == true) - preEnableIdentityCallbackDeferred = CompletableDeferred() - val preCreateIdentityCallback: PreEventCallback? = - preCreateIdentityCallback.takeIf { hasCreateIdentityCallback == true } - val preEnableIdentityCallback: PreEventCallback? = - preEnableIdentityCallback.takeIf { hasEnableIdentityCallback == true } - val context = if (enableV3 == true) context else null - val encryptionKeyBytes = - dbEncryptionKey?.foldIndexed(ByteArray(dbEncryptionKey.size)) { i, a, v -> - a.apply { set(i, v.toByte()) } - } + AsyncFunction("auth") { + { address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, enableV3: Boolean?, dbEncryptionKey: List?, dbDirectory: String?, historySyncUrl: String? -> + logV("auth") + val reactSigner = ReactNativeSigner(module = this@XMTPModule, address = address) + signer = reactSigner + + if (hasCreateIdentityCallback == true) + preCreateIdentityCallbackDeferred = CompletableDeferred() + if (hasEnableIdentityCallback == true) + preEnableIdentityCallbackDeferred = CompletableDeferred() + val preCreateIdentityCallback: PreEventCallback? = + preCreateIdentityCallback.takeIf { hasCreateIdentityCallback == true } + val preEnableIdentityCallback: PreEventCallback? = + preEnableIdentityCallback.takeIf { hasEnableIdentityCallback == true } + val context = if (enableV3 == 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, - enableV3 = enableV3 == true, - appContext = context, - dbEncryptionKey = encryptionKeyBytes, - dbDirectory = dbDirectory - ) - val client = Client().create(account = reactSigner, options = options) - clients[client.inboxId] = client - ContentJson.Companion - signer = null - sendEvent("authed", ClientWrapper.encodeToObj(client)) + val options = ClientOptions( + api = apiEnvironments(environment, appVersion), + preCreateIdentityCallback = preCreateIdentityCallback, + preEnableIdentityCallback = preEnableIdentityCallback, + enableV3 = enableV3 == true, + appContext = context, + dbEncryptionKey = encryptionKeyBytes, + dbDirectory = dbDirectory, + historySyncUrl = historySyncUrl + ) + val client = Client().create(account = reactSigner, options = options) + clients[client.inboxId] = client + ContentJson.Companion + signer = null + sendEvent("authed", ClientWrapper.encodeToObj(client)) + } } Function("receiveSignature") { requestID: String, signature: String -> @@ -260,7 +270,7 @@ class XMTPModule : Module() { } // Generate a random wallet and set the client to that - AsyncFunction("createRandom") { environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, enableV3: Boolean?, dbEncryptionKey: List?, dbDirectory: String? -> + AsyncFunction("createRandom") { environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, enableV3: Boolean?, dbEncryptionKey: List?, dbDirectory: String?, historySyncUrl: String? -> logV("createRandom") val privateKey = PrivateKeyBuilder() @@ -285,7 +295,9 @@ class XMTPModule : Module() { enableV3 = enableV3 == true, appContext = context, dbEncryptionKey = encryptionKeyBytes, - dbDirectory = dbDirectory + dbDirectory = dbDirectory, + historySyncUrl = historySyncUrl + ) val randomClient = Client().create(account = privateKey, options = options) @@ -294,7 +306,7 @@ class XMTPModule : Module() { ClientWrapper.encodeToObj(randomClient) } - AsyncFunction("createFromKeyBundle") { keyBundle: String, environment: String, appVersion: String?, enableV3: Boolean?, dbEncryptionKey: List?, dbDirectory: String? -> + AsyncFunction("createFromKeyBundle") { keyBundle: String, environment: String, appVersion: String?, enableV3: Boolean?, dbEncryptionKey: List?, dbDirectory: String?, historySyncUrl: String? -> logV("createFromKeyBundle") try { @@ -308,7 +320,8 @@ class XMTPModule : Module() { enableV3 = enableV3 == true, appContext = context, dbEncryptionKey = encryptionKeyBytes, - dbDirectory = dbDirectory + dbDirectory = dbDirectory, + historySyncUrl = historySyncUrl ) val bundle = PrivateKeyOuterClass.PrivateKeyBundle.parseFrom( From f3f0b5a326055dcff25041705118a8f9edf18239 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 24 Jun 2024 15:32:06 -0700 Subject: [PATCH 04/13] write react native side --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 10 +++- src/index.ts | 49 ++++++++++++++++--- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 6ba6f11be..55476f4ef 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -597,7 +597,10 @@ class XMTPModule : Module() { withContext(Dispatchers.IO) { logV("findV3Message") val client = clients[inboxId] ?: throw XMTPException("No client") - client.findMessage(Hex.hexStringToByteArray(messageId)) + val message = client.findMessage(Hex.hexStringToByteArray(messageId)) + message?.let { + DecodedMessageWrapper.encode(it.decrypt()) + } } } @@ -605,7 +608,10 @@ class XMTPModule : Module() { withContext(Dispatchers.IO) { logV("findGroup") val client = clients[inboxId] ?: throw XMTPException("No client") - client.findGroup(Hex.hexStringToByteArray(groupId)) + val group = client.findGroup(Hex.hexStringToByteArray(groupId)) + group?.let { + GroupWrapper.encode(client, it) + } } } diff --git a/src/index.ts b/src/index.ts index 09a41b35f..5b0054c8c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,6 +46,13 @@ export function inboxId(): string { return XMTPModule.inboxId() } +export async function findInboxIdFromAddress( + inboxId: string, + address: string +): Promise { + return XMTPModule.findInboxIdFromAddress(inboxId, address) +} + export async function deleteLocalDatabase(inboxId: string) { return XMTPModule.deleteLocalDatabase(inboxId) } @@ -58,6 +65,10 @@ export async function reconnectLocalDatabase(inboxId: string) { return XMTPModule.reconnectLocalDatabase(inboxId) } +export async function requestMessageHistorySync(inboxId: string) { + return XMTPModule.requestMessageHistorySync(inboxId) +} + export async function auth( inboxId: string, environment: 'local' | 'dev' | 'production', @@ -66,7 +77,8 @@ export async function auth( hasEnableIdentityCallback?: boolean | undefined, enableV3?: boolean | undefined, dbEncryptionKey?: Uint8Array | undefined, - dbDirectory?: string | undefined + dbDirectory?: string | undefined, + historySyncUrl?: string | undefined ) { return await XMTPModule.auth( inboxId, @@ -76,7 +88,8 @@ export async function auth( hasEnableIdentityCallback, enableV3, dbEncryptionKey ? Array.from(dbEncryptionKey) : undefined, - dbDirectory + dbDirectory, + historySyncUrl ) } @@ -91,7 +104,8 @@ export async function createRandom( hasEnableIdentityCallback?: boolean | undefined, enableV3?: boolean | undefined, dbEncryptionKey?: Uint8Array | undefined, - dbDirectory?: string | undefined + dbDirectory?: string | undefined, + historySyncUrl?: string | undefined ): Promise { return await XMTPModule.createRandom( environment, @@ -100,7 +114,8 @@ export async function createRandom( hasEnableIdentityCallback, enableV3, dbEncryptionKey ? Array.from(dbEncryptionKey) : undefined, - dbDirectory + dbDirectory, + historySyncUrl ) } @@ -110,7 +125,8 @@ export async function createFromKeyBundle( appVersion?: string | undefined, enableV3?: boolean | undefined, dbEncryptionKey?: Uint8Array | undefined, - dbDirectory?: string | undefined + dbDirectory?: string | undefined, + historySyncUrl?: string | undefined ): Promise { return await XMTPModule.createFromKeyBundle( keyBundle, @@ -118,7 +134,8 @@ export async function createFromKeyBundle( appVersion, enableV3, dbEncryptionKey ? Array.from(dbEncryptionKey) : undefined, - dbDirectory + dbDirectory, + historySyncUrl ) } @@ -207,6 +224,26 @@ export async function groupMessages< }) } +export async function findGroup< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>( + client: Client, + groupId: string +): Promise | undefined> { + const group = await XMTPModule.findGroup(client.inboxId, groupId) + return new Group(client, JSON.parse(group)) +} + +export async function findV3Message< + ContentTypes extends DefaultContentTypes = DefaultContentTypes, +>( + client: Client, + messageId: string +): Promise | undefined> { + const message = await XMTPModule.findV3Message(client.inboxId, messageId) + return DecodedMessage.from(message, client) +} + export async function syncGroups(inboxId: string) { await XMTPModule.syncGroups(inboxId) } From dc39445547ae17bbc7b7d5b7125dece0493a11f2 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 24 Jun 2024 15:44:49 -0700 Subject: [PATCH 05/13] add history sync url --- src/lib/Client.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/lib/Client.ts b/src/lib/Client.ts index c7c1b5bee..13e11cb20 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -122,7 +122,8 @@ export class Client< Boolean(enableSubscription), Boolean(options.enableV3), options.dbEncryptionKey, - options.dbDirectory + options.dbDirectory, + options.historySyncUrl ) })().catch((error) => { console.error('ERROR in create: ', error) @@ -163,7 +164,8 @@ export class Client< Boolean(enableSubscription), Boolean(options.enableV3), options.dbEncryptionKey, - options.dbDirectory + options.dbDirectory, + options.historySyncUrl ) this.removeSubscription(enableSubscription) this.removeSubscription(createSubscription) @@ -199,7 +201,8 @@ export class Client< options.appVersion, Boolean(options.enableV3), options.dbEncryptionKey, - options.dbDirectory + options.dbDirectory, + options.historySyncUrl ) return new Client( @@ -492,6 +495,10 @@ export type ClientOptions = { * OPTIONAL specify the XMTP managed database directory */ dbDirectory?: string + /** + * OPTIONAL specify a url to sync message history from + */ + historySyncUrl?: string } export type KeyType = { @@ -510,6 +517,7 @@ export function defaultOptions(opts?: Partial): ClientOptions { enableV3: false, dbEncryptionKey: undefined, dbDirectory: undefined, + historySyncUrl: undefined, } return { ..._defaultOptions, ...opts } as ClientOptions From d21d5090e281fa1cd1a5b472edd7618dc62214a5 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 24 Jun 2024 15:49:00 -0700 Subject: [PATCH 06/13] return the inbox id for address --- src/index.ts | 2 +- src/lib/Client.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 5b0054c8c..49534a6be 100644 --- a/src/index.ts +++ b/src/index.ts @@ -49,7 +49,7 @@ export function inboxId(): string { export async function findInboxIdFromAddress( inboxId: string, address: string -): Promise { +): Promise { return XMTPModule.findInboxIdFromAddress(inboxId, address) } diff --git a/src/lib/Client.ts b/src/lib/Client.ts index 13e11cb20..ca38ef872 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -349,6 +349,16 @@ export class Client< return await XMTPModule.canMessage(this.inboxId, peerAddress) } + /** + * Find the inboxId associated with this address + * + * @param {string} peerAddress - The address of the peer to check for inboxId. + * @returns {Promise} A Promise resolving to the InboxId. + */ + async findInboxIdFromAddress(peerAddress: string): Promise { + return await XMTPModule.findInboxIdFromAddress(this.inboxId, peerAddress) + } + /** * Deletes the local database. This cannot be undone and these stored messages will not be refetched from the network. */ @@ -370,6 +380,13 @@ export class Client< return await XMTPModule.reconnectLocalDatabase(this.inboxId) } + /** + * Make a request for a message history sync. + */ + async requestMessageHistorySync() { + return await XMTPModule.requestMessageHistorySync(this.inboxId) + } + /** * Determines whether the current user can send messages to the specified peers over groups. * From 4f39c2fbb79cc4db0d340f98dbc1f5df1158e48e Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 24 Jun 2024 17:29:40 -0700 Subject: [PATCH 07/13] fix: add methods for finding groups and messages --- example/ios/Podfile.lock | 14 +++++++------- ios/XMTPReactNative.podspec | 2 +- src/lib/Conversations.ts | 22 ++++++++++++++++++++++ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index d64714537..58c02bd8e 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -56,7 +56,7 @@ PODS: - hermes-engine/Pre-built (= 0.71.14) - hermes-engine/Pre-built (0.71.14) - libevent (2.1.12) - - LibXMTP (0.5.3-beta0) + - LibXMTP (0.5.3-beta1) - Logging (1.0.0) - MessagePacker (0.4.7) - MMKV (1.3.5): @@ -449,16 +449,16 @@ PODS: - GenericJSON (~> 2.0) - Logging (~> 1.0.0) - secp256k1.swift (~> 0.1) - - XMTP (0.12.2): + - XMTP (0.12.3): - Connect-Swift (= 0.12.0) - GzipSwift - - LibXMTP (= 0.5.3-beta0) + - LibXMTP (= 0.5.3-beta1) - web3.swift - XMTPReactNative (0.1.0): - ExpoModulesCore - MessagePacker - secp256k1.swift - - XMTP (= 0.12.2) + - XMTP (= 0.12.3) - Yoga (1.14.0) DEPENDENCIES: @@ -711,7 +711,7 @@ SPEC CHECKSUMS: GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - LibXMTP: c3d7b5cfa4b8df5c57ef01f09358c87dea299a13 + LibXMTP: a4e1c78fd1b174c56b764e96eff70e39c46c2499 Logging: 9ef4ecb546ad3169398d5a723bc9bea1c46bef26 MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02 MMKV: 506311d0494023c2f7e0b62cc1f31b7370fa3cfb @@ -763,8 +763,8 @@ SPEC CHECKSUMS: secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634 SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1 web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959 - XMTP: 1a25f62cf29a9ba87a879b8c85062c497c9c813f - XMTPReactNative: dbf769e050ca4214b13c096cb873feb25f0c4429 + XMTP: 5cf6c97a5cfc7295226b8e14dc079f975ea3a4be + XMTPReactNative: 520f9714e30d2909b14718654cc400473ab8d302 Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9 PODFILE CHECKSUM: 95d6ace79946933ecf80684613842ee553dd76a2 diff --git a/ios/XMTPReactNative.podspec b/ios/XMTPReactNative.podspec index 1f0ba1557..0bc569ecd 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.12.2" + s.dependency "XMTP", "= 0.12.3" end diff --git a/src/lib/Conversations.ts b/src/lib/Conversations.ts index 925b7e1cf..2e2a5b72f 100644 --- a/src/lib/Conversations.ts +++ b/src/lib/Conversations.ts @@ -94,6 +94,28 @@ export default class Conversations< return result } + /** + * This method returns a group by the group id if that group exists in the local database. + * To get the latest list of groups from the network, call syncGroups() first. + * + * @returns {Promise} A Promise that resolves to a Group or undefined if not found. + */ + async findGroup(groupId: string): Promise | undefined> { + return await XMTPModule.findGroup(this.client, groupId) + } + + /** + * This method returns a message by the message id if that message exists in the local database. + * To get the latest list of messages from the network, call syncGroups() first. + * + * @returns {Promise} A Promise that resolves to a DecodedMessage or undefined if not found. + */ + async findV3Message( + messageId: string + ): Promise | undefined> { + return await XMTPModule.findV3Message(this.client, messageId) + } + /** * This method returns a list of all conversations and groups that the client is a member of. * To include the latest groups from the network in the returned list, call syncGroups() first. From 4b9dcd57f7a156550fe905413c2d8e6cb2bbca0c Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 24 Jun 2024 17:43:23 -0700 Subject: [PATCH 08/13] add the iOS functions --- ios/XMTPModule.swift | 57 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index aca6cd6e1..653aff0b9 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -98,6 +98,13 @@ public class XMTPModule: Module { return "No Client." } } + + AsyncFunction("findInboxIdFromAddress") { (inboxId: String, address: String) -> String in + guard let client = await clientsManager.getClient(key: inboxId) else { + throw Error.noClient + } + client.inboxIdFromAddress(address: address) + } AsyncFunction("deleteLocalDatabase") { (inboxId: String) in guard let client = await clientsManager.getClient(key: inboxId) else { @@ -119,11 +126,18 @@ public class XMTPModule: Module { } try await client.reconnectLocalDatabase() } + + AsyncFunction("requestMessageHistorySync") { (inboxId: String) in + guard let client = await clientsManager.getClient(key: inboxId) else { + throw Error.noClient + } + try await client.requestMessageHistorySync() + } // // Auth functions // - AsyncFunction("auth") { (address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, enableV3: Bool?, dbEncryptionKey: [UInt8]?, dbDirectory: String?) in + AsyncFunction("auth") { (address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, enableV3: Bool?, dbEncryptionKey: [UInt8]?, dbDirectory: String?, historySyncUrl: String?) in let signer = ReactNativeSigner(module: self, address: address) self.signer = signer @@ -137,7 +151,7 @@ public class XMTPModule: Module { let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!) - let options = createClientConfig(env: environment, appVersion: appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: enableV3 == true, dbEncryptionKey: encryptionKeyData, dbDirectory: dbDirectory) + let options = createClientConfig(env: environment, appVersion: appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: enableV3 == true, dbEncryptionKey: encryptionKeyData, dbDirectory: dbDirectory, historySyncUrl: historySyncUrl) let client = try await XMTP.Client.create(account: signer, options: options) await clientsManager.updateClient(key: client.inboxID, client: client) self.signer = nil @@ -149,7 +163,7 @@ public class XMTPModule: Module { } // Generate a random wallet and set the client to that - AsyncFunction("createRandom") { (environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, enableV3: Bool?, dbEncryptionKey: [UInt8]?, dbDirectory: String?) -> [String: String] in + AsyncFunction("createRandom") { (environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, enableV3: Bool?, dbEncryptionKey: [UInt8]?, dbDirectory: String?, historySyncUrl: String?) -> [String: String] in let privateKey = try PrivateKey.generate() if(hasCreateIdentityCallback ?? false) { @@ -162,7 +176,7 @@ public class XMTPModule: Module { let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!) - let options = createClientConfig(env: environment, appVersion: appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: enableV3 == true, dbEncryptionKey: encryptionKeyData, dbDirectory: dbDirectory) + let options = createClientConfig(env: environment, appVersion: appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: enableV3 == true, dbEncryptionKey: encryptionKeyData, dbDirectory: dbDirectory, historySyncUrl: historySyncUrl) let client = try await Client.create(account: privateKey, options: options) await clientsManager.updateClient(key: client.inboxID, client: client) @@ -170,7 +184,7 @@ public class XMTPModule: Module { } // Create a client using its serialized key bundle. - AsyncFunction("createFromKeyBundle") { (keyBundle: String, environment: String, appVersion: String?, enableV3: Bool?, dbEncryptionKey: [UInt8]?, dbDirectory: String?) -> [String: String] in + AsyncFunction("createFromKeyBundle") { (keyBundle: String, environment: String, appVersion: String?, enableV3: Bool?, dbEncryptionKey: [UInt8]?, dbDirectory: String?, historySyncUrl: String?) -> [String: String] in do { guard let keyBundleData = Data(base64Encoded: keyBundle), @@ -179,7 +193,7 @@ public class XMTPModule: Module { throw Error.invalidKeyBundle } let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!) - let options = createClientConfig(env: environment, appVersion: appVersion, enableV3: enableV3 == true, dbEncryptionKey: encryptionKeyData, dbDirectory: dbDirectory) + let options = createClientConfig(env: environment, appVersion: appVersion, enableV3: enableV3 == true, dbEncryptionKey: encryptionKeyData, dbDirectory: dbDirectory, historySyncUrl: historySyncUrl) let client = try await Client.from(bundle: bundle, options: options) await clientsManager.updateClient(key: client.inboxID, client: client) return try ClientWrapper.encodeToObj(client) @@ -470,6 +484,29 @@ public class XMTPModule: Module { } } } + + AsyncFunction("findV3Message") { (inboxId: String, messageId: String) -> String? in + guard let client = await clientsManager.getClient(key: inboxId) else { + throw Error.noClient + } + if let message = try client.findMessage(messageId: Data(hex: messageId) ?? Data()) { + return try DecodedMessageWrapper.encode(message.decrypt(), client: client) + } else { + return nil + } + } + + AsyncFunction("findGroup") { (inboxId: String, groupId: String) -> String? in + guard let client = await clientsManager.getClient(key: inboxId) else { + throw Error.noClient + } + if let group = try client.findGroup(groupId: Data(hex: groupId) ?? Data()) { + return try GroupWrapper.encode(group, client: client) + } else { + return nil + } + } + AsyncFunction("loadBatchMessages") { (inboxId: String, topics: [String]) -> [String] in guard let client = await clientsManager.getClient(key: inboxId) else { @@ -1212,7 +1249,7 @@ public class XMTPModule: Module { // Helpers // - func createClientConfig(env: String, appVersion: String?, preEnableIdentityCallback: PreEventCallback? = nil, preCreateIdentityCallback: PreEventCallback? = nil, enableV3: Bool = false, dbEncryptionKey: Data? = nil, dbDirectory: String? = nil) -> XMTP.ClientOptions { + func createClientConfig(env: String, appVersion: String?, preEnableIdentityCallback: PreEventCallback? = nil, preCreateIdentityCallback: PreEventCallback? = nil, enableV3: Bool = false, dbEncryptionKey: Data? = nil, dbDirectory: String? = nil, historySyncUrl: String? = nil) -> XMTP.ClientOptions { // Ensure that all codecs have been registered. switch env { case "local": @@ -1220,19 +1257,19 @@ public class XMTPModule: Module { env: XMTP.XMTPEnvironment.local, isSecure: false, appVersion: appVersion - ), preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: enableV3, encryptionKey: dbEncryptionKey, dbDirectory: dbDirectory) + ), preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: enableV3, encryptionKey: dbEncryptionKey, dbDirectory: dbDirectory, historySyncUrl: historySyncUrl) case "production": return XMTP.ClientOptions(api: XMTP.ClientOptions.Api( env: XMTP.XMTPEnvironment.production, isSecure: true, appVersion: appVersion - ), preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: enableV3, encryptionKey: dbEncryptionKey, dbDirectory: dbDirectory) + ), preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: enableV3, encryptionKey: dbEncryptionKey, dbDirectory: dbDirectory, historySyncUrl: historySyncUrl) default: return XMTP.ClientOptions(api: XMTP.ClientOptions.Api( env: XMTP.XMTPEnvironment.dev, isSecure: true, appVersion: appVersion - ), preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: enableV3, encryptionKey: dbEncryptionKey, dbDirectory: dbDirectory) + ), preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: enableV3, encryptionKey: dbEncryptionKey, dbDirectory: dbDirectory, historySyncUrl: historySyncUrl) } } From 55d104c880c6bf79c65e456bb3958856bc444722 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 24 Jun 2024 18:39:29 -0700 Subject: [PATCH 09/13] add tests --- example/src/tests/groupTests.ts | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index 3b9d57bbb..23de88997 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -153,6 +153,14 @@ test('can drop a local database', async () => { throw new Error('should throw when local database not connected') }) +test('can get a inboxId from an address', async () => { + const [alix, bo] = await createClients(2) + + const boInboxId = await alix.findInboxIdFromAddress(bo.address) + assert(boInboxId === bo.inboxId, `${boInboxId} should match ${bo.inboxId}`) + return true +}) + test('can make a MLS V3 client from bundle', async () => { const key = new Uint8Array([ 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, @@ -293,6 +301,37 @@ test('group message delivery status', async () => { return true }) +test('can find a group by id', async () => { + const [alixClient, boClient] = await createClients(2) + const alixGroup = await alixClient.conversations.newGroup([boClient.address]) + + await boClient.conversations.syncGroups() + const boGroup = await boClient.conversations.findGroup(alixGroup.id) + + assert( + boGroup?.id === alixGroup.id, + `bo ${boGroup?.id} does not match alix ${alixGroup.id}` + ) + return true +}) + +test('can find a message by id', async () => { + const [alixClient, boClient] = await createClients(2) + const alixGroup = await alixClient.conversations.newGroup([boClient.address]) + const alixMessageId = await alixGroup.send("Hello") + + await boClient.conversations.syncGroups() + const boGroup = await boClient.conversations.findGroup(alixGroup.id) + await boGroup?.sync() + const boMessage = await boClient.conversations.findV3Message(alixMessageId) + + assert( + boMessage?.id === alixMessageId, + `bo message ${boMessage?.id} does not match ${alixMessageId}` + ) + return true +}) + test('who added me to a group', async () => { const [alixClient, boClient] = await createClients(2) await alixClient.conversations.newGroup([boClient.address]) From 8d5d6ae2f238de302b88fa8857cd92ce97ab1f04 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 24 Jun 2024 21:46:39 -0700 Subject: [PATCH 10/13] inboxid or undefined --- ios/XMTPModule.swift | 40 +++++++++++++++++++++------------------- src/index.ts | 2 +- src/lib/Client.ts | 4 +++- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index 653aff0b9..c222e2dcb 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -99,11 +99,11 @@ public class XMTPModule: Module { } } - AsyncFunction("findInboxIdFromAddress") { (inboxId: String, address: String) -> String in + AsyncFunction("findInboxIdFromAddress") { (inboxId: String, address: String) -> String? in guard let client = await clientsManager.getClient(key: inboxId) else { throw Error.noClient } - client.inboxIdFromAddress(address: address) + return try await client.inboxIdFromAddress(address: address) } AsyncFunction("deleteLocalDatabase") { (inboxId: String) in @@ -137,25 +137,27 @@ public class XMTPModule: Module { // // Auth functions // - AsyncFunction("auth") { (address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, enableV3: Bool?, dbEncryptionKey: [UInt8]?, dbDirectory: String?, historySyncUrl: String?) in + AsyncFunction("auth") { + { (address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, enableV3: Bool?, dbEncryptionKey: [UInt8]?, dbDirectory: String?, historySyncUrl: String?) in - let signer = ReactNativeSigner(module: self, address: address) - self.signer = signer - if(hasCreateIdentityCallback ?? false) { - preCreateIdentityCallbackDeferred = DispatchSemaphore(value: 0) - } - if(hasEnableIdentityCallback ?? false) { - preEnableIdentityCallbackDeferred = DispatchSemaphore(value: 0) + let signer = ReactNativeSigner(module: self, address: address) + self.signer = signer + if(hasCreateIdentityCallback ?? false) { + self.preCreateIdentityCallbackDeferred = DispatchSemaphore(value: 0) + } + if(hasEnableIdentityCallback ?? false) { + self.preEnableIdentityCallbackDeferred = DispatchSemaphore(value: 0) + } + let preCreateIdentityCallback: PreEventCallback? = hasCreateIdentityCallback ?? false ? self.preCreateIdentityCallback : nil + let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil + let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!) + + let options = self.createClientConfig(env: environment, appVersion: appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: enableV3 == true, dbEncryptionKey: encryptionKeyData, dbDirectory: dbDirectory, historySyncUrl: historySyncUrl) + let client = try await XMTP.Client.create(account: signer, options: options) + await self.clientsManager.updateClient(key: client.inboxID, client: client) + self.signer = nil + self.sendEvent("authed", try ClientWrapper.encodeToObj(client)) } - let preCreateIdentityCallback: PreEventCallback? = hasCreateIdentityCallback ?? false ? self.preCreateIdentityCallback : nil - let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil - let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!) - - let options = createClientConfig(env: environment, appVersion: appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: enableV3 == true, dbEncryptionKey: encryptionKeyData, dbDirectory: dbDirectory, historySyncUrl: historySyncUrl) - let client = try await XMTP.Client.create(account: signer, options: options) - await clientsManager.updateClient(key: client.inboxID, client: client) - self.signer = nil - sendEvent("authed", try ClientWrapper.encodeToObj(client)) } Function("receiveSignature") { (requestID: String, signature: String) in diff --git a/src/index.ts b/src/index.ts index 49534a6be..0ef3de8ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -49,7 +49,7 @@ export function inboxId(): string { export async function findInboxIdFromAddress( inboxId: string, address: string -): Promise { +): Promise { return XMTPModule.findInboxIdFromAddress(inboxId, address) } diff --git a/src/lib/Client.ts b/src/lib/Client.ts index ca38ef872..864baea88 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -355,7 +355,9 @@ export class Client< * @param {string} peerAddress - The address of the peer to check for inboxId. * @returns {Promise} A Promise resolving to the InboxId. */ - async findInboxIdFromAddress(peerAddress: string): Promise { + async findInboxIdFromAddress( + peerAddress: string + ): Promise { return await XMTPModule.findInboxIdFromAddress(this.inboxId, peerAddress) } From fae16ea224b20181c61bd922ca6055e977eb8ad7 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 24 Jun 2024 22:49:35 -0700 Subject: [PATCH 11/13] get it working on android --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 42 +++++++------ .../wrappers/AuthParamsWrapper.kt | 24 ++++++++ ios/Wrappers/AuthParamsWrapper.swift | 46 ++++++++++++++ ios/XMTPModule.swift | 49 +++++++-------- src/index.ts | 61 ++++++++++++++----- 5 files changed, 163 insertions(+), 59 deletions(-) create mode 100644 android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/AuthParamsWrapper.kt create mode 100644 ios/Wrappers/AuthParamsWrapper.swift diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 55476f4ef..4982cfff2 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -13,6 +13,7 @@ import expo.modules.kotlin.exception.Exceptions import expo.modules.kotlin.functions.Coroutine import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition +import expo.modules.xmtpreactnativesdk.wrappers.AuthParamsWrapper import expo.modules.xmtpreactnativesdk.wrappers.ClientWrapper import expo.modules.xmtpreactnativesdk.wrappers.ConsentWrapper import expo.modules.xmtpreactnativesdk.wrappers.ConsentWrapper.Companion.consentStateToString @@ -227,10 +228,11 @@ class XMTPModule : Module() { // Auth functions // AsyncFunction("auth") { - { address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, enableV3: Boolean?, dbEncryptionKey: List?, dbDirectory: String?, historySyncUrl: String? -> + { address: String, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, dbEncryptionKey: List?, authParams: String -> logV("auth") val reactSigner = ReactNativeSigner(module = this@XMTPModule, address = address) signer = reactSigner + val authOptions = AuthParamsWrapper.authParamsFromJson(authParams) if (hasCreateIdentityCallback == true) preCreateIdentityCallbackDeferred = CompletableDeferred() @@ -240,21 +242,21 @@ class XMTPModule : Module() { preCreateIdentityCallback.takeIf { hasCreateIdentityCallback == true } val preEnableIdentityCallback: PreEventCallback? = preEnableIdentityCallback.takeIf { hasEnableIdentityCallback == true } - val context = if (enableV3 == true) context else null + val context = if (authOptions.enableV3) 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), + api = apiEnvironments(authOptions.environment, authOptions.appVersion), preCreateIdentityCallback = preCreateIdentityCallback, preEnableIdentityCallback = preEnableIdentityCallback, - enableV3 = enableV3 == true, + enableV3 = authOptions.enableV3, appContext = context, dbEncryptionKey = encryptionKeyBytes, - dbDirectory = dbDirectory, - historySyncUrl = historySyncUrl + dbDirectory = authOptions.dbDirectory, + historySyncUrl = authOptions.historySyncUrl ) val client = Client().create(account = reactSigner, options = options) clients[client.inboxId] = client @@ -270,7 +272,7 @@ class XMTPModule : Module() { } // Generate a random wallet and set the client to that - AsyncFunction("createRandom") { environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, enableV3: Boolean?, dbEncryptionKey: List?, dbDirectory: String?, historySyncUrl: String? -> + AsyncFunction("createRandom") { hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, dbEncryptionKey: List?, authParams: String -> logV("createRandom") val privateKey = PrivateKeyBuilder() @@ -282,21 +284,23 @@ class XMTPModule : Module() { preCreateIdentityCallback.takeIf { hasCreateIdentityCallback == true } val preEnableIdentityCallback: PreEventCallback? = preEnableIdentityCallback.takeIf { hasEnableIdentityCallback == true } - val context = if (enableV3 == true) context else null + + val authOptions = AuthParamsWrapper.authParamsFromJson(authParams) + val context = if (authOptions.enableV3) 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), + api = apiEnvironments(authOptions.environment, authOptions.appVersion), preCreateIdentityCallback = preCreateIdentityCallback, preEnableIdentityCallback = preEnableIdentityCallback, - enableV3 = enableV3 == true, + enableV3 = authOptions.enableV3, appContext = context, dbEncryptionKey = encryptionKeyBytes, - dbDirectory = dbDirectory, - historySyncUrl = historySyncUrl + dbDirectory = authOptions.dbDirectory, + historySyncUrl = authOptions.historySyncUrl ) val randomClient = Client().create(account = privateKey, options = options) @@ -306,22 +310,22 @@ class XMTPModule : Module() { ClientWrapper.encodeToObj(randomClient) } - AsyncFunction("createFromKeyBundle") { keyBundle: String, environment: String, appVersion: String?, enableV3: Boolean?, dbEncryptionKey: List?, dbDirectory: String?, historySyncUrl: String? -> + AsyncFunction("createFromKeyBundle") { keyBundle: String, dbEncryptionKey: List?, authParams: String -> logV("createFromKeyBundle") - + val authOptions = AuthParamsWrapper.authParamsFromJson(authParams) try { - val context = if (enableV3 == true) context else null + val context = if (authOptions.enableV3) 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), - enableV3 = enableV3 == true, + api = apiEnvironments(authOptions.environment, authOptions.appVersion), + enableV3 = authOptions.enableV3, appContext = context, dbEncryptionKey = encryptionKeyBytes, - dbDirectory = dbDirectory, - historySyncUrl = historySyncUrl + dbDirectory = authOptions.dbDirectory, + historySyncUrl = authOptions.historySyncUrl ) val bundle = PrivateKeyOuterClass.PrivateKeyBundle.parseFrom( diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/AuthParamsWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/AuthParamsWrapper.kt new file mode 100644 index 000000000..99146a715 --- /dev/null +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/AuthParamsWrapper.kt @@ -0,0 +1,24 @@ +package expo.modules.xmtpreactnativesdk.wrappers + +import com.google.gson.JsonParser + +class AuthParamsWrapper( + val environment: String, + val appVersion: String?, + val enableV3: Boolean = false, + val dbDirectory: String?, + val historySyncUrl: String?, +) { + companion object { + fun authParamsFromJson(authParams: String): AuthParamsWrapper { + val jsonOptions = JsonParser.parseString(authParams).asJsonObject + return AuthParamsWrapper( + jsonOptions.get("environment").asString, + if (jsonOptions.has("appVersion")) jsonOptions.get("appVersion").asString else null, + if (jsonOptions.has("enableV3")) jsonOptions.get("enableV3").asBoolean else false, + if (jsonOptions.has("dbDirectory")) jsonOptions.get("dbDirectory").asString else null, + if (jsonOptions.has("historySyncUrl")) jsonOptions.get("historySyncUrl").asString else null + ) + } + } +} diff --git a/ios/Wrappers/AuthParamsWrapper.swift b/ios/Wrappers/AuthParamsWrapper.swift new file mode 100644 index 000000000..47f1c48f1 --- /dev/null +++ b/ios/Wrappers/AuthParamsWrapper.swift @@ -0,0 +1,46 @@ +// +// AuthParamsWrapper.swift +// XMTPReactNative +// +// Created by Naomi Plasterer on 6/24/24. +// + +import Foundation +import XMTP + +class AuthParamsWrapper { + let environment: String + let appVersion: String? + let enableV3: Bool + let dbDirectory: String? + let historySyncUrl: String? + + init(environment: String, appVersion: String?, enableV3: Bool, dbDirectory: String?, historySyncUrl: String?) { + self.environment = environment + self.appVersion = appVersion + self.enableV3 = enableV3 + self.dbDirectory = dbDirectory + self.historySyncUrl = historySyncUrl + } + + static func authParamsFromJson(_ authParams: String) -> AuthParamsWrapper? { + guard let data = authParams.data(using: .utf8), + let jsonOptions = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { + return nil + } + + let environment = jsonOptions["environment"] as? String ?? "" + let appVersion = jsonOptions["appVersion"] as? String + let enableV3 = jsonOptions["enableV3"] as? Bool ?? false + let dbDirectory = jsonOptions["dbDirectory"] as? String + let historySyncUrl = jsonOptions["historySyncUrl"] as? String + + return AuthParamsWrapper( + environment: environment, + appVersion: appVersion, + enableV3: enableV3, + dbDirectory: dbDirectory, + historySyncUrl: historySyncUrl + ) + } +} diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index c222e2dcb..37a33171e 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -137,27 +137,25 @@ public class XMTPModule: Module { // // Auth functions // - AsyncFunction("auth") { - { (address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, enableV3: Bool?, dbEncryptionKey: [UInt8]?, dbDirectory: String?, historySyncUrl: String?) in - - let signer = ReactNativeSigner(module: self, address: address) - self.signer = signer - if(hasCreateIdentityCallback ?? false) { - self.preCreateIdentityCallbackDeferred = DispatchSemaphore(value: 0) - } - if(hasEnableIdentityCallback ?? false) { - self.preEnableIdentityCallbackDeferred = DispatchSemaphore(value: 0) - } - let preCreateIdentityCallback: PreEventCallback? = hasCreateIdentityCallback ?? false ? self.preCreateIdentityCallback : nil - let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil - let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!) - - let options = self.createClientConfig(env: environment, appVersion: appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: enableV3 == true, dbEncryptionKey: encryptionKeyData, dbDirectory: dbDirectory, historySyncUrl: historySyncUrl) - let client = try await XMTP.Client.create(account: signer, options: options) - await self.clientsManager.updateClient(key: client.inboxID, client: client) - self.signer = nil - self.sendEvent("authed", try ClientWrapper.encodeToObj(client)) + AsyncFunction("auth") { (address: String, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, dbEncryptionKey: [UInt8]?, authParams: String) in + let signer = ReactNativeSigner(module: self, address: address) + self.signer = signer + if(hasCreateIdentityCallback ?? false) { + self.preCreateIdentityCallbackDeferred = DispatchSemaphore(value: 0) + } + if(hasEnableIdentityCallback ?? false) { + self.preEnableIdentityCallbackDeferred = DispatchSemaphore(value: 0) } + let preCreateIdentityCallback: PreEventCallback? = hasCreateIdentityCallback ?? false ? self.preCreateIdentityCallback : nil + let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil + let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!) + let authOptions = AuthParamsWrapper.authParamsFromJson(authParams) + + let options = self.createClientConfig(env: authOptions.environment, appVersion: authOptions?.appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: authOptions.enableV3, dbEncryptionKey: encryptionKeyData, dbDirectory: authOptions?.dbDirectory, historySyncUrl: authOptions?.historySyncUrl) + let client = try await XMTP.Client.create(account: signer, options: options) + await self.clientsManager.updateClient(key: client.inboxID, client: client) + self.signer = nil + self.sendEvent("authed", try ClientWrapper.encodeToObj(client)) } Function("receiveSignature") { (requestID: String, signature: String) in @@ -165,7 +163,7 @@ public class XMTPModule: Module { } // Generate a random wallet and set the client to that - AsyncFunction("createRandom") { (environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, enableV3: Bool?, dbEncryptionKey: [UInt8]?, dbDirectory: String?, historySyncUrl: String?) -> [String: String] in + AsyncFunction("createRandom") { (hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, dbEncryptionKey: [UInt8]?, authParams: String) -> [String: String] in let privateKey = try PrivateKey.generate() if(hasCreateIdentityCallback ?? false) { @@ -177,8 +175,9 @@ public class XMTPModule: Module { let preCreateIdentityCallback: PreEventCallback? = hasCreateIdentityCallback ?? false ? self.preCreateIdentityCallback : nil let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!) + let authOptions = AuthParamsWrapper.authParamsFromJson(authParams) - let options = createClientConfig(env: environment, appVersion: appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: enableV3 == true, dbEncryptionKey: encryptionKeyData, dbDirectory: dbDirectory, historySyncUrl: historySyncUrl) + let options = createClientConfig(env: authOptions.environment, appVersion: authOptions.appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: authOptions.enableV3, dbEncryptionKey: encryptionKeyData, dbDirectory: authOptions.dbDirectory, historySyncUrl: authOptions.historySyncUrl) let client = try await Client.create(account: privateKey, options: options) await clientsManager.updateClient(key: client.inboxID, client: client) @@ -186,7 +185,7 @@ public class XMTPModule: Module { } // Create a client using its serialized key bundle. - AsyncFunction("createFromKeyBundle") { (keyBundle: String, environment: String, appVersion: String?, enableV3: Bool?, dbEncryptionKey: [UInt8]?, dbDirectory: String?, historySyncUrl: String?) -> [String: String] in + AsyncFunction("createFromKeyBundle") { (keyBundle: String, dbEncryptionKey: [UInt8]?, authParams: String) -> [String: String] in do { guard let keyBundleData = Data(base64Encoded: keyBundle), @@ -195,7 +194,9 @@ public class XMTPModule: Module { throw Error.invalidKeyBundle } let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!) - let options = createClientConfig(env: environment, appVersion: appVersion, enableV3: enableV3 == true, dbEncryptionKey: encryptionKeyData, dbDirectory: dbDirectory, historySyncUrl: historySyncUrl) + let authOptions = AuthParamsWrapper.authParamsFromJson(authParams) + + let options = createClientConfig(env: authOptions.environment, appVersion: authOptions.appVersion, enableV3: authOptions.enableV3, dbEncryptionKey: encryptionKeyData, dbDirectory: authOptions.dbDirectory, historySyncUrl: authOptions.historySyncUrl) let client = try await Client.from(bundle: bundle, options: options) await clientsManager.updateClient(key: client.inboxID, client: client) return try ClientWrapper.encodeToObj(client) diff --git a/src/index.ts b/src/index.ts index 0ef3de8ba..26b2cab1a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -70,7 +70,7 @@ export async function requestMessageHistorySync(inboxId: string) { } export async function auth( - inboxId: string, + address: string, environment: 'local' | 'dev' | 'production', appVersion?: string | undefined, hasCreateIdentityCallback?: boolean | undefined, @@ -80,16 +80,23 @@ export async function auth( dbDirectory?: string | undefined, historySyncUrl?: string | undefined ) { - return await XMTPModule.auth( - inboxId, + const encryptionKey = dbEncryptionKey + ? Array.from(dbEncryptionKey) + : undefined + + const authParams: AuthParams = { environment, appVersion, - hasCreateIdentityCallback, - hasEnableIdentityCallback, enableV3, - dbEncryptionKey ? Array.from(dbEncryptionKey) : undefined, dbDirectory, - historySyncUrl + historySyncUrl, + } + return await XMTPModule.auth( + address, + hasCreateIdentityCallback, + hasEnableIdentityCallback, + encryptionKey, + JSON.stringify(authParams) ) } @@ -107,15 +114,22 @@ export async function createRandom( dbDirectory?: string | undefined, historySyncUrl?: string | undefined ): Promise { - return await XMTPModule.createRandom( + const encryptionKey = dbEncryptionKey + ? Array.from(dbEncryptionKey) + : undefined + + const authParams: AuthParams = { environment, appVersion, - hasCreateIdentityCallback, - hasEnableIdentityCallback, enableV3, - dbEncryptionKey ? Array.from(dbEncryptionKey) : undefined, dbDirectory, - historySyncUrl + historySyncUrl, + } + return await XMTPModule.createRandom( + hasCreateIdentityCallback, + hasEnableIdentityCallback, + encryptionKey, + JSON.stringify(authParams) ) } @@ -128,14 +142,21 @@ export async function createFromKeyBundle( dbDirectory?: string | undefined, historySyncUrl?: string | undefined ): Promise { - return await XMTPModule.createFromKeyBundle( - keyBundle, + const encryptionKey = dbEncryptionKey + ? Array.from(dbEncryptionKey) + : undefined + + const authParams: AuthParams = { environment, appVersion, enableV3, - dbEncryptionKey ? Array.from(dbEncryptionKey) : undefined, dbDirectory, - historySyncUrl + historySyncUrl, + } + return await XMTPModule.createFromKeyBundle( + keyBundle, + encryptionKey, + JSON.stringify(authParams) ) } @@ -927,6 +948,14 @@ export async function processWelcomeMessage< export const emitter = new EventEmitter(XMTPModule ?? NativeModulesProxy.XMTP) +interface AuthParams { + environment: string + appVersion?: string + enableV3?: boolean + dbDirectory?: string + historySyncUrl?: string +} + export * from './XMTP.types' export { Client } from './lib/Client' export * from './lib/ContentCodec' From 3cfb728b2d496818e5977803a3d2e658d0769bfb Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 24 Jun 2024 22:59:51 -0700 Subject: [PATCH 12/13] getting closer to working --- ios/Wrappers/AuthParamsWrapper.swift | 8 ++++---- ios/XMTPModule.swift | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ios/Wrappers/AuthParamsWrapper.swift b/ios/Wrappers/AuthParamsWrapper.swift index 47f1c48f1..763071ee8 100644 --- a/ios/Wrappers/AuthParamsWrapper.swift +++ b/ios/Wrappers/AuthParamsWrapper.swift @@ -8,7 +8,7 @@ import Foundation import XMTP -class AuthParamsWrapper { +struct AuthParamsWrapper { let environment: String let appVersion: String? let enableV3: Bool @@ -23,13 +23,13 @@ class AuthParamsWrapper { self.historySyncUrl = historySyncUrl } - static func authParamsFromJson(_ authParams: String) -> AuthParamsWrapper? { + static func authParamsFromJson(_ authParams: String) -> AuthParamsWrapper { guard let data = authParams.data(using: .utf8), let jsonOptions = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { - return nil + return AuthParamsWrapper(environment: "dev", appVersion: nil, enableV3: false, dbDirectory: nil, historySyncUrl: nil) } - let environment = jsonOptions["environment"] as? String ?? "" + let environment = jsonOptions["environment"] as? String ?? "dev" let appVersion = jsonOptions["appVersion"] as? String let enableV3 = jsonOptions["enableV3"] as? Bool ?? false let dbDirectory = jsonOptions["dbDirectory"] as? String diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index 37a33171e..7020d8199 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -151,7 +151,7 @@ public class XMTPModule: Module { let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!) let authOptions = AuthParamsWrapper.authParamsFromJson(authParams) - let options = self.createClientConfig(env: authOptions.environment, appVersion: authOptions?.appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: authOptions.enableV3, dbEncryptionKey: encryptionKeyData, dbDirectory: authOptions?.dbDirectory, historySyncUrl: authOptions?.historySyncUrl) + let options = self.createClientConfig(env: authOptions.environment, appVersion: authOptions.appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, enableV3: authOptions.enableV3, dbEncryptionKey: encryptionKeyData, dbDirectory: authOptions.dbDirectory, historySyncUrl: authOptions.historySyncUrl) let client = try await XMTP.Client.create(account: signer, options: options) await self.clientsManager.updateClient(key: client.inboxID, client: client) self.signer = nil From 6b84b6942681c673098a47e960d763bd2077d297 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 24 Jun 2024 23:01:08 -0700 Subject: [PATCH 13/13] remove extra brackets --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 70 +++++++++---------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 4982cfff2..b0955bccb 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -227,43 +227,41 @@ class XMTPModule : Module() { // // Auth functions // - AsyncFunction("auth") { - { address: String, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, dbEncryptionKey: List?, authParams: String -> - logV("auth") - val reactSigner = ReactNativeSigner(module = this@XMTPModule, address = address) - signer = reactSigner - val authOptions = AuthParamsWrapper.authParamsFromJson(authParams) - - if (hasCreateIdentityCallback == true) - preCreateIdentityCallbackDeferred = CompletableDeferred() - if (hasEnableIdentityCallback == true) - preEnableIdentityCallbackDeferred = CompletableDeferred() - val preCreateIdentityCallback: PreEventCallback? = - preCreateIdentityCallback.takeIf { hasCreateIdentityCallback == true } - val preEnableIdentityCallback: PreEventCallback? = - preEnableIdentityCallback.takeIf { hasEnableIdentityCallback == true } - val context = if (authOptions.enableV3) context else null - val encryptionKeyBytes = - dbEncryptionKey?.foldIndexed(ByteArray(dbEncryptionKey.size)) { i, a, v -> - a.apply { set(i, v.toByte()) } - } + AsyncFunction("auth") { address: String, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, dbEncryptionKey: List?, authParams: String -> + logV("auth") + val reactSigner = ReactNativeSigner(module = this@XMTPModule, address = address) + signer = reactSigner + val authOptions = AuthParamsWrapper.authParamsFromJson(authParams) - val options = ClientOptions( - api = apiEnvironments(authOptions.environment, authOptions.appVersion), - preCreateIdentityCallback = preCreateIdentityCallback, - preEnableIdentityCallback = preEnableIdentityCallback, - enableV3 = authOptions.enableV3, - appContext = context, - dbEncryptionKey = encryptionKeyBytes, - dbDirectory = authOptions.dbDirectory, - historySyncUrl = authOptions.historySyncUrl - ) - val client = Client().create(account = reactSigner, options = options) - clients[client.inboxId] = client - ContentJson.Companion - signer = null - sendEvent("authed", ClientWrapper.encodeToObj(client)) - } + if (hasCreateIdentityCallback == true) + preCreateIdentityCallbackDeferred = CompletableDeferred() + if (hasEnableIdentityCallback == true) + preEnableIdentityCallbackDeferred = CompletableDeferred() + val preCreateIdentityCallback: PreEventCallback? = + preCreateIdentityCallback.takeIf { hasCreateIdentityCallback == true } + val preEnableIdentityCallback: PreEventCallback? = + preEnableIdentityCallback.takeIf { hasEnableIdentityCallback == true } + val context = if (authOptions.enableV3) context else null + val encryptionKeyBytes = + dbEncryptionKey?.foldIndexed(ByteArray(dbEncryptionKey.size)) { i, a, v -> + a.apply { set(i, v.toByte()) } + } + + val options = ClientOptions( + api = apiEnvironments(authOptions.environment, authOptions.appVersion), + preCreateIdentityCallback = preCreateIdentityCallback, + preEnableIdentityCallback = preEnableIdentityCallback, + enableV3 = authOptions.enableV3, + appContext = context, + dbEncryptionKey = encryptionKeyBytes, + dbDirectory = authOptions.dbDirectory, + historySyncUrl = authOptions.historySyncUrl + ) + val client = Client().create(account = reactSigner, options = options) + clients[client.inboxId] = client + ContentJson.Companion + signer = null + sendEvent("authed", ClientWrapper.encodeToObj(client)) } Function("receiveSignature") { requestID: String, signature: String ->