Skip to content

Commit

Permalink
Merge pull request #430 from xmtp/np/database-fetch-enhancements
Browse files Browse the repository at this point in the history
Add new fetch methods for V3 local DB
  • Loading branch information
nplasterer authored Jun 25, 2024
2 parents 1a67fc4 + 6b84b69 commit 018a965
Show file tree
Hide file tree
Showing 11 changed files with 375 additions and 67 deletions.
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
90 changes: 67 additions & 23 deletions android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@ 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.AuthParamsWrapper
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
Expand Down Expand Up @@ -49,10 +53,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
Expand All @@ -66,12 +72,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<String, Continuation<Signature>> = mutableMapOf()
Expand Down Expand Up @@ -190,6 +190,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())
Expand All @@ -209,13 +217,21 @@ 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<Int>?, dbDirectory: String? ->
AsyncFunction("auth") { address: String, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, dbEncryptionKey: List<Int>?, authParams: String ->
logV("auth")
val reactSigner = ReactNativeSigner(module = this@XMTPModule, address = address)
signer = reactSigner
val authOptions = AuthParamsWrapper.authParamsFromJson(authParams)

if (hasCreateIdentityCallback == true)
preCreateIdentityCallbackDeferred = CompletableDeferred()
Expand All @@ -225,20 +241,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
dbDirectory = authOptions.dbDirectory,
historySyncUrl = authOptions.historySyncUrl
)
val client = Client().create(account = reactSigner, options = options)
clients[client.inboxId] = client
Expand All @@ -253,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<Int>?, dbDirectory: String? ->
AsyncFunction("createRandom") { hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, dbEncryptionKey: List<Int>?, authParams: String ->
logV("createRandom")
val privateKey = PrivateKeyBuilder()

Expand All @@ -265,20 +282,24 @@ 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
dbDirectory = authOptions.dbDirectory,
historySyncUrl = authOptions.historySyncUrl

)
val randomClient = Client().create(account = privateKey, options = options)

Expand All @@ -287,21 +308,22 @@ class XMTPModule : Module() {
ClientWrapper.encodeToObj(randomClient)
}

AsyncFunction("createFromKeyBundle") { keyBundle: String, environment: String, appVersion: String?, enableV3: Boolean?, dbEncryptionKey: List<Int>?, dbDirectory: String? ->
AsyncFunction("createFromKeyBundle") { keyBundle: String, dbEncryptionKey: List<Int>?, 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
dbDirectory = authOptions.dbDirectory,
historySyncUrl = authOptions.historySyncUrl
)
val bundle =
PrivateKeyOuterClass.PrivateKeyBundle.parseFrom(
Expand Down Expand Up @@ -573,6 +595,28 @@ class XMTPModule : Module() {
}
}

AsyncFunction("findV3Message") Coroutine { inboxId: String, messageId: String ->
withContext(Dispatchers.IO) {
logV("findV3Message")
val client = clients[inboxId] ?: throw XMTPException("No client")
val message = client.findMessage(Hex.hexStringToByteArray(messageId))
message?.let {
DecodedMessageWrapper.encode(it.decrypt())
}
}
}

AsyncFunction("findGroup") Coroutine { inboxId: String, groupId: String ->
withContext(Dispatchers.IO) {
logV("findGroup")
val client = clients[inboxId] ?: throw XMTPException("No client")
val group = client.findGroup(Hex.hexStringToByteArray(groupId))
group?.let {
GroupWrapper.encode(client, it)
}
}
}

AsyncFunction("loadBatchMessages") Coroutine { inboxId: String, topics: List<String> ->
withContext(Dispatchers.IO) {
logV("loadBatchMessages")
Expand Down
Original file line number Diff line number Diff line change
@@ -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
)
}
}
}
14 changes: 7 additions & 7 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -711,7 +711,7 @@ SPEC CHECKSUMS:
GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa
hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
LibXMTP: c3d7b5cfa4b8df5c57ef01f09358c87dea299a13
LibXMTP: a4e1c78fd1b174c56b764e96eff70e39c46c2499
Logging: 9ef4ecb546ad3169398d5a723bc9bea1c46bef26
MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02
MMKV: 506311d0494023c2f7e0b62cc1f31b7370fa3cfb
Expand Down Expand Up @@ -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
Expand Down
39 changes: 39 additions & 0 deletions example/src/tests/groupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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])
Expand Down
46 changes: 46 additions & 0 deletions ios/Wrappers/AuthParamsWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// AuthParamsWrapper.swift
// XMTPReactNative
//
// Created by Naomi Plasterer on 6/24/24.
//

import Foundation
import XMTP

struct 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 AuthParamsWrapper(environment: "dev", appVersion: nil, enableV3: false, dbDirectory: nil, historySyncUrl: nil)
}

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
let historySyncUrl = jsonOptions["historySyncUrl"] as? String

return AuthParamsWrapper(
environment: environment,
appVersion: appVersion,
enableV3: enableV3,
dbDirectory: dbDirectory,
historySyncUrl: historySyncUrl
)
}
}
Loading

0 comments on commit 018a965

Please sign in to comment.