From eb0d094b570401d8524f517d4ef54a7ceb5e559d Mon Sep 17 00:00:00 2001 From: cameronvoell Date: Wed, 31 Jul 2024 16:39:09 -0700 Subject: [PATCH] Adds pre auth to inbox callback --- android/build.gradle | 22 +++--- .../modules/xmtpreactnativesdk/XMTPModule.kt | 27 +++++++- example/src/LaunchScreen.tsx | 7 ++ example/src/tests/groupTests.ts | 42 +++++++++++ src/index.ts | 8 +++ src/lib/Client.ts | 69 ++++++++++++------- 6 files changed, 137 insertions(+), 38 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 43c5ec20a..45b0ce764 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -98,19 +98,19 @@ 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.13" 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" // xmtp-android local testing setup below (comment org.xmtp:android above) - // implementation files('/xmtp-android/library/build/outputs/aar/library-debug.aar') - // implementation 'com.google.crypto.tink:tink-android:1.8.0' - // implementation 'io.grpc:grpc-kotlin-stub:1.4.1' - // implementation 'io.grpc:grpc-okhttp:1.62.2' - // implementation 'io.grpc:grpc-protobuf-lite:1.62.2' - // implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0' - // implementation 'org.web3j:crypto:5.0.0' - // implementation "net.java.dev.jna:jna:5.14.0@aar" - // api 'com.google.protobuf:protobuf-kotlin-lite:3.22.3' - // api 'org.xmtp:proto-kotlin:3.62.1' + implementation files('/Users/cameronvoell/XMTP/xmtp-android/library/build/outputs/aar/library-debug.aar') + implementation 'com.google.crypto.tink:tink-android:1.8.0' + implementation 'io.grpc:grpc-kotlin-stub:1.4.1' + implementation 'io.grpc:grpc-okhttp:1.62.2' + implementation 'io.grpc:grpc-protobuf-lite:1.62.2' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0' + implementation 'org.web3j:crypto:5.0.0' + implementation "net.java.dev.jna:jna:5.14.0@aar" + api 'com.google.protobuf:protobuf-kotlin-lite:3.22.3' + api 'org.xmtp:proto-kotlin:3.62.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..52a246ba9 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -155,6 +155,7 @@ class XMTPModule : Module() { private val subscriptions: MutableMap = mutableMapOf() private var preEnableIdentityCallbackDeferred: CompletableDeferred? = null private var preCreateIdentityCallbackDeferred: CompletableDeferred? = null + private var preAuthenticateToInboxCallbackDeferred: CompletableDeferred? = null override fun definition() = ModuleDefinition { @@ -165,6 +166,7 @@ class XMTPModule : Module() { "authed", "preCreateIdentityCallback", "preEnableIdentityCallback", + "preAuthenticateToInboxCallback", // Conversations "conversation", "group", @@ -227,7 +229,7 @@ class XMTPModule : Module() { // // Auth functions // - AsyncFunction("auth") Coroutine { address: String, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, dbEncryptionKey: List?, authParams: String -> + AsyncFunction("auth") Coroutine { address: String, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, hasAuthInboxCallback: Boolean?, dbEncryptionKey: List?, authParams: String -> withContext(Dispatchers.IO) { logV("auth") @@ -239,10 +241,14 @@ class XMTPModule : Module() { preCreateIdentityCallbackDeferred = CompletableDeferred() if (hasEnableIdentityCallback == true) preEnableIdentityCallbackDeferred = CompletableDeferred() + if (hasAuthInboxCallback == true) + preAuthenticateToInboxCallbackDeferred = CompletableDeferred() val preCreateIdentityCallback: PreEventCallback? = preCreateIdentityCallback.takeIf { hasCreateIdentityCallback == true } val preEnableIdentityCallback: PreEventCallback? = preEnableIdentityCallback.takeIf { hasEnableIdentityCallback == true } + val preAuthenticateToInboxCallback: PreEventCallback? = + preAuthenticateToInboxCallback.takeIf { hasAuthInboxCallback == true } val context = if (authOptions.enableV3) context else null val encryptionKeyBytes = dbEncryptionKey?.foldIndexed(ByteArray(dbEncryptionKey.size)) { i, a, v -> @@ -253,6 +259,7 @@ class XMTPModule : Module() { api = apiEnvironments(authOptions.environment, authOptions.appVersion), preCreateIdentityCallback = preCreateIdentityCallback, preEnableIdentityCallback = preEnableIdentityCallback, + preAuthenticateToInboxCallback = preAuthenticateToInboxCallback, enableV3 = authOptions.enableV3, appContext = context, dbEncryptionKey = encryptionKeyBytes, @@ -273,7 +280,7 @@ class XMTPModule : Module() { } // Generate a random wallet and set the client to that - AsyncFunction("createRandom") Coroutine { hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, dbEncryptionKey: List?, authParams: String -> + AsyncFunction("createRandom") Coroutine { hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, hasPreAuthenticateToInboxCallback: Boolean?, dbEncryptionKey: List?, authParams: String -> withContext(Dispatchers.IO) { logV("createRandom") val privateKey = PrivateKeyBuilder() @@ -282,10 +289,14 @@ class XMTPModule : Module() { preCreateIdentityCallbackDeferred = CompletableDeferred() if (hasEnableIdentityCallback == true) preEnableIdentityCallbackDeferred = CompletableDeferred() + if (hasPreAuthenticateToInboxCallback == true) + preAuthenticateToInboxCallbackDeferred = CompletableDeferred() val preCreateIdentityCallback: PreEventCallback? = preCreateIdentityCallback.takeIf { hasCreateIdentityCallback == true } val preEnableIdentityCallback: PreEventCallback? = preEnableIdentityCallback.takeIf { hasEnableIdentityCallback == true } + val preAuthenticateToInboxCallback: PreEventCallback? = + preAuthenticateToInboxCallback.takeIf { hasPreAuthenticateToInboxCallback == true } val authOptions = AuthParamsWrapper.authParamsFromJson(authParams) val context = if (authOptions.enableV3) context else null @@ -298,6 +309,7 @@ class XMTPModule : Module() { api = apiEnvironments(authOptions.environment, authOptions.appVersion), preCreateIdentityCallback = preCreateIdentityCallback, preEnableIdentityCallback = preEnableIdentityCallback, + preAuthenticateToInboxCallback = preAuthenticateToInboxCallback, enableV3 = authOptions.enableV3, appContext = context, dbEncryptionKey = encryptionKeyBytes, @@ -1524,6 +1536,11 @@ class XMTPModule : Module() { preEnableIdentityCallbackDeferred?.complete(Unit) } + Function("preAuthenticateToInboxCallbackCompleted") { + logV("preAuthenticateToInboxCallbackCompleted") + preAuthenticateToInboxCallbackDeferred?.complete(Unit) + } + AsyncFunction("allowGroups") Coroutine { inboxId: String, groupIds: List -> withContext(Dispatchers.IO) { logV("allowGroups") @@ -1844,6 +1861,12 @@ class XMTPModule : Module() { preCreateIdentityCallbackDeferred?.await() preCreateIdentityCallbackDeferred = null } + + private val preAuthenticateToInboxCallback: suspend () -> Unit = { + sendEvent("preAuthenticateToInboxCallback") + preAuthenticateToInboxCallbackDeferred?.await() + preAuthenticateToInboxCallbackDeferred = null + } } diff --git a/example/src/LaunchScreen.tsx b/example/src/LaunchScreen.tsx index 27a387bc3..48d8f7ab3 100644 --- a/example/src/LaunchScreen.tsx +++ b/example/src/LaunchScreen.tsx @@ -99,6 +99,10 @@ export default function LaunchScreen( console.log('Pre Enable Identity Callback') } + const preAuthenticateToInboxCallback = async () => { + console.log('Pre Authenticate To Inbox Callback') + } + const networkOptions = [ { key: 0, label: 'dev' }, { key: 1, label: 'local' }, @@ -222,8 +226,10 @@ export default function LaunchScreen( XMTP.Client.create(signer, { env: selectedNetwork, appVersion, + codecs: supportedCodecs, preCreateIdentityCallback, preEnableIdentityCallback, + preAuthenticateToInboxCallback, enableV3: enableGroups === 'true', dbEncryptionKey, }) @@ -258,6 +264,7 @@ export default function LaunchScreen( codecs: supportedCodecs, preCreateIdentityCallback, preEnableIdentityCallback, + preAuthenticateToInboxCallback, enableV3: enableGroups === 'true', dbEncryptionKey, }) diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index 0bca66bf2..2154278ac 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -41,6 +41,48 @@ test('can make a MLS V3 client', async () => { return true }) + +test('calls preAuthenticateToInboxCallback when supplied', async () => { + let isCallbackCalled = 0 + let isPreAuthCalled = false + const preAuthenticateToInboxCallback = () => { + isCallbackCalled++; + isPreAuthCalled = true + } + const preEnableIdentityCallback = () => { + isCallbackCalled++ + } + const preCreateIdentityCallback = () => { + isCallbackCalled++ + } + const keyBytes = new Uint8Array([ + 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({ + env: 'local', + enableV3: true, + preEnableIdentityCallback, + preCreateIdentityCallback, + preAuthenticateToInboxCallback, + dbEncryptionKey: keyBytes, + }) + + assert( + isCallbackCalled === 3, + `callback should be called 3 times but was ${isCallbackCalled}` + ) + + if (!isPreAuthCalled) { + throw new Error('preAuthenticateToInboxCallback not called') + } + + return true +}) + test('can delete a local database', async () => { let [client, anotherClient] = await createClients(2) diff --git a/src/index.ts b/src/index.ts index 564421978..7b0dfdb2b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -76,6 +76,7 @@ export async function auth( appVersion?: string | undefined, hasCreateIdentityCallback?: boolean | undefined, hasEnableIdentityCallback?: boolean | undefined, + hasPreAuthenticateToInboxCallback?: boolean | undefined, enableV3?: boolean | undefined, dbEncryptionKey?: Uint8Array | undefined, dbDirectory?: string | undefined, @@ -96,6 +97,7 @@ export async function auth( address, hasCreateIdentityCallback, hasEnableIdentityCallback, + hasPreAuthenticateToInboxCallback, encryptionKey, JSON.stringify(authParams) ) @@ -110,6 +112,7 @@ export async function createRandom( appVersion?: string | undefined, hasCreateIdentityCallback?: boolean | undefined, hasEnableIdentityCallback?: boolean | undefined, + hasPreAuthenticateToInboxCallback?: boolean | undefined, enableV3?: boolean | undefined, dbEncryptionKey?: Uint8Array | undefined, dbDirectory?: string | undefined, @@ -129,6 +132,7 @@ export async function createRandom( return await XMTPModule.createRandom( hasCreateIdentityCallback, hasEnableIdentityCallback, + hasPreAuthenticateToInboxCallback, encryptionKey, JSON.stringify(authParams) ) @@ -863,6 +867,10 @@ export function preCreateIdentityCallbackCompleted() { XMTPModule.preCreateIdentityCallbackCompleted() } +export function preAuthenticateToInboxCallbackCompleted() { + XMTPModule.preAuthenticateToInboxCallbackCompleted() +} + export async function isGroupActive( inboxId: string, id: string diff --git a/src/lib/Client.ts b/src/lib/Client.ts index 7dd46c66f..48e960775 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -61,7 +61,7 @@ export class Client< ) { throw new Error('Must pass an encryption key that is exactly 32 bytes.') } - const { enableSubscription, createSubscription } = + const { enableSubscription, createSubscription, authInboxSubscription } = this.setupSubscriptions(options) const signer = getSigner(wallet) if (!signer) { @@ -89,10 +89,11 @@ export class Client< } catch (e) { const errorMessage = 'ERROR in create. User rejected signature' console.info(errorMessage, e) - this.removeSubscription(enableSubscription) - this.removeSubscription(createSubscription) - this.removeSignSubscription() - this.removeAuthSubscription() + this.removeAllSubscriptions( + createSubscription, + enableSubscription, + authInboxSubscription + ) reject(errorMessage) } } @@ -105,10 +106,11 @@ export class Client< address: string installationId: string }) => { - this.removeSubscription(enableSubscription) - this.removeSubscription(createSubscription) - this.removeSignSubscription() - this.removeAuthSubscription() + this.removeAllSubscriptions( + createSubscription, + enableSubscription, + authInboxSubscription + ) resolve( new Client( message.address, @@ -125,29 +127,33 @@ export class Client< options.appVersion, Boolean(createSubscription), Boolean(enableSubscription), + Boolean(authInboxSubscription), Boolean(options.enableV3), options.dbEncryptionKey, options.dbDirectory, options.historySyncUrl ) })().catch((error) => { + this.removeAllSubscriptions( + createSubscription, + enableSubscription, + authInboxSubscription + ) console.error('ERROR in create: ', error) }) }) } - private static removeSignSubscription(): void { - if (this.signSubscription) { - this.signSubscription.remove() - this.signSubscription = null - } - } - - private static removeAuthSubscription(): void { - if (this.authSubscription) { - this.authSubscription.remove() - this.authSubscription = null - } + private static removeAllSubscriptions( + createSubscription?: Subscription, + enableSubscription?: Subscription, + authInboxSubscription?: Subscription + ): void { + [createSubscription, enableSubscription, authInboxSubscription, this.signSubscription, this.authSubscription] + .forEach(subscription => subscription?.remove()); + + this.signSubscription = null; + this.authSubscription = null; } /** @@ -166,20 +172,22 @@ export class Client< ) { throw new Error('Must pass an encryption key that is exactly 32 bytes.') } - const { enableSubscription, createSubscription } = + const { createSubscription, enableSubscription, authInboxSubscription } = this.setupSubscriptions(options) const client = await XMTPModule.createRandom( options.env, options.appVersion, Boolean(createSubscription), Boolean(enableSubscription), + Boolean(authInboxSubscription), Boolean(options.enableV3), options.dbEncryptionKey, options.dbDirectory, options.historySyncUrl ) - this.removeSubscription(enableSubscription) this.removeSubscription(createSubscription) + this.removeSubscription(enableSubscription) + this.removeSubscription(authInboxSubscription) return new Client( client['address'], @@ -279,8 +287,9 @@ export class Client< } private static setupSubscriptions(opts: ClientOptions): { - enableSubscription?: Subscription createSubscription?: Subscription + enableSubscription?: Subscription + authInboxSubscription?: Subscription } { const enableSubscription = this.addSubscription( 'preEnableIdentityCallback', @@ -300,7 +309,16 @@ export class Client< } ) - return { enableSubscription, createSubscription } + const authInboxSubscription = this.addSubscription( + 'preAuthenticateToInboxCallback', + opts, + async () => { + await this.executeCallback(opts?.preAuthenticateToInboxCallback) + XMTPModule.preAuthenticateToInboxCallbackCompleted() + } + ) + + return { createSubscription, enableSubscription, authInboxSubscription } } constructor( @@ -519,6 +537,7 @@ export type ClientOptions = { */ preCreateIdentityCallback?: () => Promise | void preEnableIdentityCallback?: () => Promise | void + preAuthenticateToInboxCallback?: () => Promise | void /** * Specify whether to enable V3 version of MLS (Group Chat) */