From 3650b8d17b97790644c5fab27dbae02a23d26d9c Mon Sep 17 00:00:00 2001 From: cameronvoell Date: Fri, 22 Dec 2023 13:43:16 -0500 Subject: [PATCH 1/4] fix: sign actions should wait for pre-callback completion before proceeding --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 30 +++++++++++++++++++ src/index.ts | 8 +++++ src/lib/Client.ts | 2 ++ 3 files changed, 40 insertions(+) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index c531c9016..10b6e2853 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -18,6 +18,7 @@ import expo.modules.xmtpreactnativesdk.wrappers.DecryptedLocalAttachment import expo.modules.xmtpreactnativesdk.wrappers.EncryptedLocalAttachment import expo.modules.xmtpreactnativesdk.wrappers.PreparedLocalMessage import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -126,6 +127,8 @@ class XMTPModule : Module() { private val isDebugEnabled = BuildConfig.DEBUG // TODO: consider making this configurable private val conversations: MutableMap = mutableMapOf() private val subscriptions: MutableMap = mutableMapOf() + var waitForPreEnableIdentityCallback: Boolean = false + var waitForPreCreateIdentityCallback: Boolean = false override fun definition() = ModuleDefinition { Name("XMTP") @@ -151,6 +154,9 @@ class XMTPModule : Module() { logV("auth") val reactSigner = ReactNativeSigner(module = this@XMTPModule, address = address) signer = reactSigner + + waitForPreEnableIdentityCallback = hasEnableIdentityCallback == true + waitForPreCreateIdentityCallback = hasCreateIdentityCallback == true val preCreateIdentityCallback: PreEventCallback? = preCreateIdentityCallback.takeIf { hasCreateIdentityCallback == true } val preEnableIdentityCallback: PreEventCallback? = @@ -175,6 +181,9 @@ class XMTPModule : Module() { AsyncFunction("createRandom") { environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean? -> logV("createRandom") val privateKey = PrivateKeyBuilder() + + waitForPreEnableIdentityCallback = hasEnableIdentityCallback == true + waitForPreCreateIdentityCallback = hasCreateIdentityCallback == true val preCreateIdentityCallback: PreEventCallback? = preCreateIdentityCallback.takeIf { hasCreateIdentityCallback == true } val preEnableIdentityCallback: PreEventCallback? = @@ -590,6 +599,18 @@ class XMTPModule : Module() { val client = clients[clientAddress] ?: throw XMTPException("No client") client.contacts.consentList.entries.map { ConsentWrapper.encode(it.value) } } + + Function("preEnableIdentityCallbackCompleted") { + logV("preEnableIdentityCallbackCompleted") + waitForPreEnableIdentityCallback = false + true + } + + Function("preCreateIdentityCallbackCompleted") { + logV("preCreateIdentityCallbackCompleted") + waitForPreCreateIdentityCallback = false + true + } } // @@ -715,10 +736,19 @@ class XMTPModule : Module() { private val preEnableIdentityCallback: suspend () -> Unit = { sendEvent("preEnableIdentityCallback") + waitForCallback { waitForPreEnableIdentityCallback } } private val preCreateIdentityCallback: suspend () -> Unit = { sendEvent("preCreateIdentityCallback") + waitForCallback { waitForPreCreateIdentityCallback } + } + + // Helper function to wait for a callback + private suspend fun waitForCallback(check: () -> Boolean) { + while (check()) { + delay(100) // Wait for 100ms before checking again + } } } diff --git a/src/index.ts b/src/index.ts index 5e324ad8b..80c8a5834 100644 --- a/src/index.ts +++ b/src/index.ts @@ -393,6 +393,14 @@ export async function consentList( }) } +export function preEnableIdentityCallbackCompleted() { + XMTPModule.preEnableIdentityCallbackCompleted() +} + +export function preCreateIdentityCallbackCompleted() { + XMTPModule.preCreateIdentityCallbackCompleted() +} + export const emitter = new EventEmitter(XMTPModule ?? NativeModulesProxy.XMTP) export * from './lib/ContentCodec' diff --git a/src/lib/Client.ts b/src/lib/Client.ts index 745e333b2..8ea8b96b5 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -236,6 +236,7 @@ export class Client { opts, async () => { await this.executeCallback(opts?.preEnableIdentityCallback) + XMTPModule.preEnableIdentityCallbackCompleted() } ) @@ -244,6 +245,7 @@ export class Client { opts, async () => { await this.executeCallback(opts?.preCreateIdentityCallback) + XMTPModule.preCreateIdentityCallbackCompleted() } ) From 08649d7fba6bbe7b5eb5b4f057b9acb4298df75c Mon Sep 17 00:00:00 2001 From: cameronvoell Date: Fri, 22 Dec 2023 14:31:57 -0500 Subject: [PATCH 2/4] Clean up kotlin code --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 10b6e2853..ac17a845a 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -17,8 +17,8 @@ import expo.modules.xmtpreactnativesdk.wrappers.DecodedMessageWrapper import expo.modules.xmtpreactnativesdk.wrappers.DecryptedLocalAttachment import expo.modules.xmtpreactnativesdk.wrappers.EncryptedLocalAttachment import expo.modules.xmtpreactnativesdk.wrappers.PreparedLocalMessage +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -127,8 +127,9 @@ class XMTPModule : Module() { private val isDebugEnabled = BuildConfig.DEBUG // TODO: consider making this configurable private val conversations: MutableMap = mutableMapOf() private val subscriptions: MutableMap = mutableMapOf() - var waitForPreEnableIdentityCallback: Boolean = false - var waitForPreCreateIdentityCallback: Boolean = false + private var preEnableIdentityCallbackDeferred: CompletableDeferred? = null + private var preCreateIdentityCallbackDeferred: CompletableDeferred? = null + override fun definition() = ModuleDefinition { Name("XMTP") @@ -155,8 +156,10 @@ class XMTPModule : Module() { val reactSigner = ReactNativeSigner(module = this@XMTPModule, address = address) signer = reactSigner - waitForPreEnableIdentityCallback = hasEnableIdentityCallback == true - waitForPreCreateIdentityCallback = hasCreateIdentityCallback == true + if (hasCreateIdentityCallback == true) + preCreateIdentityCallbackDeferred = CompletableDeferred() + if (hasEnableIdentityCallback == true) + preEnableIdentityCallbackDeferred = CompletableDeferred() val preCreateIdentityCallback: PreEventCallback? = preCreateIdentityCallback.takeIf { hasCreateIdentityCallback == true } val preEnableIdentityCallback: PreEventCallback? = @@ -182,8 +185,10 @@ class XMTPModule : Module() { logV("createRandom") val privateKey = PrivateKeyBuilder() - waitForPreEnableIdentityCallback = hasEnableIdentityCallback == true - waitForPreCreateIdentityCallback = hasCreateIdentityCallback == true + if (hasCreateIdentityCallback == true) + preCreateIdentityCallbackDeferred = CompletableDeferred() + if (hasEnableIdentityCallback == true) + preEnableIdentityCallbackDeferred = CompletableDeferred() val preCreateIdentityCallback: PreEventCallback? = preCreateIdentityCallback.takeIf { hasCreateIdentityCallback == true } val preEnableIdentityCallback: PreEventCallback? = @@ -600,16 +605,14 @@ class XMTPModule : Module() { client.contacts.consentList.entries.map { ConsentWrapper.encode(it.value) } } - Function("preEnableIdentityCallbackCompleted") { - logV("preEnableIdentityCallbackCompleted") - waitForPreEnableIdentityCallback = false - true - } - Function("preCreateIdentityCallbackCompleted") { logV("preCreateIdentityCallbackCompleted") - waitForPreCreateIdentityCallback = false - true + preCreateIdentityCallbackDeferred?.complete(Unit) + } + + Function("preEnableIdentityCallbackCompleted") { + logV("preEnableIdentityCallbackCompleted") + preEnableIdentityCallbackDeferred?.complete(Unit) } } @@ -736,19 +739,14 @@ class XMTPModule : Module() { private val preEnableIdentityCallback: suspend () -> Unit = { sendEvent("preEnableIdentityCallback") - waitForCallback { waitForPreEnableIdentityCallback } + preEnableIdentityCallbackDeferred?.await() + preCreateIdentityCallbackDeferred == null } private val preCreateIdentityCallback: suspend () -> Unit = { sendEvent("preCreateIdentityCallback") - waitForCallback { waitForPreCreateIdentityCallback } - } - - // Helper function to wait for a callback - private suspend fun waitForCallback(check: () -> Boolean) { - while (check()) { - delay(100) // Wait for 100ms before checking again - } + preCreateIdentityCallbackDeferred?.await() + preCreateIdentityCallbackDeferred = null } } From d5f25570c00bbf280f65c6acbb50625a7303afdc Mon Sep 17 00:00:00 2001 From: kele-leanes Date: Wed, 27 Dec 2023 11:01:21 -0300 Subject: [PATCH 3/4] ios: wait for pre-event callback completion --- ios/XMTPModule.swift | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index 8387ab56d..afb26ebe0 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -28,6 +28,8 @@ public class XMTPModule: Module { let clientsManager = ClientsManager() let conversationsManager = IsolatedManager() let subscriptionsManager = IsolatedManager>() + var preEnableIdentityCallbackDeferred: DispatchGroup? + var preCreateIdentityCallbackDeferred: DispatchGroup? actor ClientsManager { private var clients: [String: XMTP.Client] = [:] @@ -67,6 +69,12 @@ public class XMTPModule: Module { AsyncFunction("auth") { (address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?) in let signer = ReactNativeSigner(module: self, address: address) self.signer = signer + if(hasCreateIdentityCallback ?? false) { + preCreateIdentityCallbackDeferred = DispatchGroup() + } + if(hasEnableIdentityCallback ?? false) { + preEnableIdentityCallbackDeferred = DispatchGroup() + } let preCreateIdentityCallback: PreEventCallback? = hasCreateIdentityCallback ?? false ? self.preCreateIdentityCallback : nil let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil let options = createClientConfig(env: environment, appVersion: appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback) @@ -82,6 +90,12 @@ public class XMTPModule: Module { // Generate a random wallet and set the client to that AsyncFunction("createRandom") { (environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?) -> String in let privateKey = try PrivateKey.generate() + if(hasCreateIdentityCallback ?? false) { + preCreateIdentityCallbackDeferred = DispatchGroup() + } + if(hasEnableIdentityCallback ?? false) { + preEnableIdentityCallbackDeferred = DispatchGroup() + } let preCreateIdentityCallback: PreEventCallback? = hasCreateIdentityCallback ?? false ? self.preCreateIdentityCallback : nil let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil @@ -534,6 +548,14 @@ public class XMTPModule: Module { try ConsentWrapper.encode(entry.value) } } + + Function("preEnableIdentityCallbackCompleted") { + preEnableIdentityCallbackDeferred?.leave() + } + + Function("preCreateIdentityCallbackCompleted") { + preCreateIdentityCallbackDeferred?.leave() + } } // @@ -673,9 +695,15 @@ public class XMTPModule: Module { func preEnableIdentityCallback() { sendEvent("preEnableIdentityCallback") + preEnableIdentityCallbackDeferred?.enter() + preEnableIdentityCallbackDeferred?.wait() + preCreateIdentityCallbackDeferred = nil } func preCreateIdentityCallback() { sendEvent("preCreateIdentityCallback") + preCreateIdentityCallbackDeferred?.enter() + preCreateIdentityCallbackDeferred?.wait() + preEnableIdentityCallbackDeferred = nil } } From 85607166b2b9c8fce49f80e692443bcd37d263c9 Mon Sep 17 00:00:00 2001 From: kele-leanes Date: Wed, 3 Jan 2024 14:53:19 -0300 Subject: [PATCH 4/4] Update DispatchGroup to DispatchSemaphore in XMTPModule.swift --- ios/XMTPModule.swift | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index afb26ebe0..0c521997d 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -28,8 +28,8 @@ public class XMTPModule: Module { let clientsManager = ClientsManager() let conversationsManager = IsolatedManager() let subscriptionsManager = IsolatedManager>() - var preEnableIdentityCallbackDeferred: DispatchGroup? - var preCreateIdentityCallbackDeferred: DispatchGroup? + private var preEnableIdentityCallbackDeferred: DispatchSemaphore? + private var preCreateIdentityCallbackDeferred: DispatchSemaphore? actor ClientsManager { private var clients: [String: XMTP.Client] = [:] @@ -70,10 +70,10 @@ public class XMTPModule: Module { let signer = ReactNativeSigner(module: self, address: address) self.signer = signer if(hasCreateIdentityCallback ?? false) { - preCreateIdentityCallbackDeferred = DispatchGroup() + preCreateIdentityCallbackDeferred = DispatchSemaphore(value: 0) } if(hasEnableIdentityCallback ?? false) { - preEnableIdentityCallbackDeferred = DispatchGroup() + preEnableIdentityCallbackDeferred = DispatchSemaphore(value: 0) } let preCreateIdentityCallback: PreEventCallback? = hasCreateIdentityCallback ?? false ? self.preCreateIdentityCallback : nil let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil @@ -91,10 +91,10 @@ public class XMTPModule: Module { AsyncFunction("createRandom") { (environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?) -> String in let privateKey = try PrivateKey.generate() if(hasCreateIdentityCallback ?? false) { - preCreateIdentityCallbackDeferred = DispatchGroup() + preCreateIdentityCallbackDeferred = DispatchSemaphore(value: 0) } if(hasEnableIdentityCallback ?? false) { - preEnableIdentityCallbackDeferred = DispatchGroup() + preEnableIdentityCallbackDeferred = DispatchSemaphore(value: 0) } let preCreateIdentityCallback: PreEventCallback? = hasCreateIdentityCallback ?? false ? self.preCreateIdentityCallback : nil let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil @@ -550,11 +550,15 @@ public class XMTPModule: Module { } Function("preEnableIdentityCallbackCompleted") { - preEnableIdentityCallbackDeferred?.leave() + DispatchQueue.global().async { + self.preEnableIdentityCallbackDeferred?.signal() + } } Function("preCreateIdentityCallbackCompleted") { - preCreateIdentityCallbackDeferred?.leave() + DispatchQueue.global().async { + self.preCreateIdentityCallbackDeferred?.signal() + } } } @@ -695,15 +699,13 @@ public class XMTPModule: Module { func preEnableIdentityCallback() { sendEvent("preEnableIdentityCallback") - preEnableIdentityCallbackDeferred?.enter() - preEnableIdentityCallbackDeferred?.wait() - preCreateIdentityCallbackDeferred = nil + self.preEnableIdentityCallbackDeferred?.wait() + self.preCreateIdentityCallbackDeferred = nil } func preCreateIdentityCallback() { sendEvent("preCreateIdentityCallback") - preCreateIdentityCallbackDeferred?.enter() - preCreateIdentityCallbackDeferred?.wait() - preEnableIdentityCallbackDeferred = nil + self.preCreateIdentityCallbackDeferred?.wait() + self.preEnableIdentityCallbackDeferred = nil } }