From 1bf65a9d789d18ab033b8492b4f763dbe210ac1f Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 25 Nov 2024 17:00:58 -0800 Subject: [PATCH] put in place the infrastructure --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 48 +++++++++++ example/src/tests/conversationTests.ts | 86 +++++++++++++++++++ src/index.ts | 36 ++++++++ 3 files changed, 170 insertions(+) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 3f0d9a18..2b06026b 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -1298,6 +1298,54 @@ class XMTPModule : Module() { } } } + + // FOR TESTING ONLY + AsyncFunction("createRandomWalletKeyForLocalTesting") Coroutine { -> + withContext(Dispatchers.IO) { + val privateKeyBuilder = PrivateKeyBuilder() + val privateKey = privateKeyBuilder.getPrivateKey().toByteArray() + privateKey.map { it.toInt() and 0xFF } + } + } + + AsyncFunction("createForLocalTesting") Coroutine { dbEncryptionKey: List, authParams: String, walletKey: List? -> + withContext(Dispatchers.IO) { + logV("createForLocalTesting") + val privateKey = if (walletKey != null) { + val walletKeyBytes = walletKey.foldIndexed(ByteArray(walletKey.size)) { i, a, v -> + a.apply { set(i, v.toByte()) } + } + val pk = PrivateKeyBuilder.buildFromPrivateKeyData(walletKeyBytes) + PrivateKeyBuilder(pk) + } else { + PrivateKeyBuilder() + } + + val authOptions = AuthParamsWrapper.authParamsFromJson(authParams) + if (authOptions.environment != "local") throw XMTPException("Only enabled on local") + val options = clientOptions( + dbEncryptionKey, + authParams, + ) + val randomClient = Client().create(account = privateKey, options = options) + + ContentJson.Companion + clients[randomClient.installationId] = randomClient + clients[randomClient.inboxId] = randomClient + ClientWrapper.encodeToObj(randomClient) + } + } + + AsyncFunction("localTestingSyncAllConversations") Coroutine { installationId: String -> + withContext(Dispatchers.IO) { + logV("localTestingSyncAllConversations") + val client = clients[installationId] ?: throw XMTPException("No client") + client.conversations.sync() + val numGroupsSyncedInt: Int = + client.conversations.syncAllConversations().toInt() + numGroupsSyncedInt + } + } } // diff --git a/example/src/tests/conversationTests.ts b/example/src/tests/conversationTests.ts index 5012c14c..13a02a01 100644 --- a/example/src/tests/conversationTests.ts +++ b/example/src/tests/conversationTests.ts @@ -1,9 +1,16 @@ +import RNFS from 'react-native-fs' import { Test, assert, createClients, delayToPropogate } from './test-utils' import { + Client, + ConsentRecord, Conversation, ConversationId, ConversationVersion, + createForLocalTesting, + createRandomWalletKeyForLocalTesting, + localTestingSyncAllConversations, } from '../../../src/index' +import { Wallet } from 'ethers' export const conversationTests: Test[] = [] let counter = 1 @@ -547,3 +554,82 @@ test('can streamAllMessages from multiple clients - swapped', async () => { return true }) + +test('can sync consent', async () => { + const [bo] = await createClients(1) + 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, + ]) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const dbDirPath = `${RNFS.DocumentDirectoryPath}/xmtp_db` + const dbDirPath2 = `${RNFS.DocumentDirectoryPath}/xmtp_db2` + const directoryExists = await RNFS.exists(dbDirPath) + if (!directoryExists) { + await RNFS.mkdir(dbDirPath) + } + const directoryExists2 = await RNFS.exists(dbDirPath2) + if (!directoryExists2) { + await RNFS.mkdir(dbDirPath2) + } + const alixWallet = await createRandomWalletKeyForLocalTesting() + + const alix = await createForLocalTesting( + keyBytes, + dbDirPath, + undefined, + alixWallet + ) + + // Create DM conversation + const dm = await alix.conversations.findOrCreateDm(bo.address) + await dm.updateConsent('denied') + const consentState = await dm.consentState() + assert(consentState === 'denied', `Expected 'denied', got ${consentState}`) + + await bo.conversations.sync() + const boDm = await bo.conversations.findConversation(dm.id) + + const alix2 = await createForLocalTesting( + keyBytes, + dbDirPath2, + undefined, + alixWallet + ) + + const state = await alix2.inboxState(true) + assert( + state.installations.length === 2, + `Expected 2 installations, got ${state.installations.length}` + ) + + // Sync conversations + await bo.conversations.sync() + if (boDm) await boDm.sync() + await localTestingSyncAllConversations(alix.installationId) + await localTestingSyncAllConversations(alix2.installationId) + await alix2.preferences.syncConsent() + await localTestingSyncAllConversations(alix.installationId) + await delayToPropogate(2000) + await localTestingSyncAllConversations(alix2.installationId) + await delayToPropogate(2000) + + const dm2 = await alix2.conversations.findConversation(dm.id) + const consentState2 = await dm2?.consentState() + assert(consentState2 === 'denied', `Expected 'denied', got ${consentState2}`) + + await alix2.preferences.setConsentState( + new ConsentRecord(dm2!.id, 'conversation_id', 'allowed') + ) + + const convoState = await alix2.preferences.conversationConsentState(dm2!.id) + assert(convoState === 'allowed', `Expected 'allowed', got ${convoState}`) + + const updatedConsentState = await dm2?.consentState() + assert( + updatedConsentState === 'allowed', + `Expected 'allowed', got ${updatedConsentState}` + ) + + return true +}) diff --git a/src/index.ts b/src/index.ts index 3375a32d..ffd1dc80 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1053,6 +1053,42 @@ export async function exportNativeLogs() { return XMTPModule.exportNativeLogs() } +export async function createRandomWalletKeyForLocalTesting(): Promise { + const walletKey = await XMTPModule.createRandomWalletKeyForLocalTesting() + return new Uint8Array(walletKey) +} + +export async function createForLocalTesting( + dbEncryptionKey: Uint8Array, + dbDirectory?: string | undefined, + historySyncUrl?: string | undefined, + walletKey?: Uint8Array +): Promise> { + const authParams: AuthParams = { + environment: 'local', + dbDirectory, + historySyncUrl, + } + const privateKey = walletKey ? Array.from(walletKey) : undefined + const client = await XMTPModule.createForLocalTesting( + Array.from(dbEncryptionKey), + JSON.stringify(authParams), + privateKey + ) + + return new Client( + client['address'], + client['inboxId'], + client['installationId'], + client['dbPath'], + [] + ) +} + +export async function localTestingSyncAllConversations(installationId: string) { + return XMTPModule.localTestingSyncAllConversations(installationId) +} + export const emitter = new EventEmitter(XMTPModule ?? NativeModulesProxy.XMTP) interface AuthParams {