Skip to content

Commit

Permalink
Merge pull request #438 from xmtp/np/optimistic-sending
Browse files Browse the repository at this point in the history
Optimistic Sendings
  • Loading branch information
nplasterer authored Jul 9, 2024
2 parents a4b8fc0 + 4dec9af commit 90828ef
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 78 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.14.3"
implementation "org.xmtp:android:0.14.6"
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
76 changes: 53 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,7 +6,6 @@ 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
Expand Down Expand Up @@ -62,8 +61,6 @@ 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.android.library.hexToByteArray
import org.xmtp.proto.keystore.api.v1.Keystore.TopicMap.TopicData
import org.xmtp.proto.message.api.v1.MessageApiOuterClass
import org.xmtp.proto.message.contents.Invitation.ConsentProofPayload
Expand Down Expand Up @@ -608,7 +605,7 @@ class XMTPModule : Module() {
withContext(Dispatchers.IO) {
logV("findV3Message")
val client = clients[inboxId] ?: throw XMTPException("No client")
val message = client.findMessage(Hex.hexStringToByteArray(messageId))
val message = client.findMessage(messageId)
message?.let {
DecodedMessageWrapper.encode(it.decrypt())
}
Expand All @@ -619,7 +616,7 @@ class XMTPModule : Module() {
withContext(Dispatchers.IO) {
logV("findGroup")
val client = clients[inboxId] ?: throw XMTPException("No client")
val group = client.findGroup(Hex.hexStringToByteArray(groupId))
val group = client.findGroup(groupId)
group?.let {
GroupWrapper.encode(client, it)
}
Expand Down Expand Up @@ -707,6 +704,37 @@ class XMTPModule : Module() {
}
}

AsyncFunction("publishPreparedGroupMessages") Coroutine { inboxId: String, groupId: String ->
withContext(Dispatchers.IO) {
logV("publishPreparedGroupMessages")
val group =
findGroup(
inboxId = inboxId,
id = groupId
)
?: throw XMTPException("no group found for $groupId")

group.publishMessages()
}
}

AsyncFunction("prepareGroupMessage") Coroutine { inboxId: String, id: String, contentJson: String ->
withContext(Dispatchers.IO) {
logV("prepareGroupMessage")
val group =
findGroup(
inboxId = inboxId,
id = id
)
?: throw XMTPException("no group found for $id")
val sending = ContentJson.fromJson(contentJson)
group.prepareMessage(
content = sending.content,
options = SendOptions(contentType = sending.type)
)
}
}

AsyncFunction("prepareMessage") Coroutine { inboxId: String, conversationTopic: String, contentJson: String ->
withContext(Dispatchers.IO) {
logV("prepareMessage")
Expand Down Expand Up @@ -843,7 +871,8 @@ class XMTPModule : Module() {
"admin_only" -> GroupPermissionPreconfiguration.ADMIN_ONLY
else -> GroupPermissionPreconfiguration.ALL_MEMBERS
}
val createGroupParams = CreateGroupParamsWrapper.createGroupParamsFromJson(groupOptionsJson)
val createGroupParams =
CreateGroupParamsWrapper.createGroupParamsFromJson(groupOptionsJson)
val group = client.conversations.newGroup(
peerAddresses,
permissionLevel,
Expand Down Expand Up @@ -1125,7 +1154,7 @@ class XMTPModule : Module() {
logV("updateAddMemberPermission")
val client = clients[clientInboxId] ?: throw XMTPException("No client")
val group = findGroup(clientInboxId, id)

group?.updateAddMemberPermission(getPermissionOption(newPermission))
}
}
Expand All @@ -1135,7 +1164,7 @@ class XMTPModule : Module() {
logV("updateRemoveMemberPermission")
val client = clients[clientInboxId] ?: throw XMTPException("No client")
val group = findGroup(clientInboxId, id)

group?.updateRemoveMemberPermission(getPermissionOption(newPermission))
}
}
Expand All @@ -1145,7 +1174,7 @@ class XMTPModule : Module() {
logV("updateAddAdminPermission")
val client = clients[clientInboxId] ?: throw XMTPException("No client")
val group = findGroup(clientInboxId, id)

group?.updateAddAdminPermission(getPermissionOption(newPermission))
}
}
Expand All @@ -1155,7 +1184,7 @@ class XMTPModule : Module() {
logV("updateRemoveAdminPermission")
val client = clients[clientInboxId] ?: throw XMTPException("No client")
val group = findGroup(clientInboxId, id)

group?.updateRemoveAdminPermission(getPermissionOption(newPermission))
}
}
Expand Down Expand Up @@ -1471,29 +1500,31 @@ class XMTPModule : Module() {
}

AsyncFunction("allowGroups") Coroutine { inboxId: String, groupIds: List<String> ->
logV("allowGroups")
val client = clients[inboxId] ?: throw XMTPException("No client")
val groupDataIds = groupIds.map { Hex.hexStringToByteArray(it) }
client.contacts.allowGroups(groupDataIds)
withContext(Dispatchers.IO) {
logV("allowGroups")
val client = clients[inboxId] ?: throw XMTPException("No client")
client.contacts.allowGroups(groupIds)
}
}

AsyncFunction("denyGroups") Coroutine { inboxId: String, groupIds: List<String> ->
logV("denyGroups")
val client = clients[inboxId] ?: throw XMTPException("No client")
val groupDataIds = groupIds.map { Hex.hexStringToByteArray(it) }
client.contacts.denyGroups(groupDataIds)
withContext(Dispatchers.IO) {
logV("denyGroups")
val client = clients[inboxId] ?: throw XMTPException("No client")
client.contacts.denyGroups(groupIds)
}
}

AsyncFunction("isGroupAllowed") { inboxId: String, groupId: String ->
logV("isGroupAllowed")
val client = clients[inboxId] ?: throw XMTPException("No client")
client.contacts.isGroupAllowed(groupId.hexToByteArray())
client.contacts.isGroupAllowed(groupId)
}

AsyncFunction("isGroupDenied") { inboxId: String, groupId: String ->
logV("isGroupDenied")
val client = clients[inboxId] ?: throw XMTPException("No client")
client.contacts.isGroupDenied(groupId.hexToByteArray())
client.contacts.isGroupDenied(groupId)
}
}

Expand Down Expand Up @@ -1532,7 +1563,7 @@ class XMTPModule : Module() {
return null
}

private suspend fun findGroup(
private fun findGroup(
inboxId: String,
id: String,
): Group? {
Expand All @@ -1543,8 +1574,7 @@ class XMTPModule : Module() {
if (cacheGroup != null) {
return cacheGroup
} else {
val group = client.conversations.listGroups()
.firstOrNull { it.id.toHex() == id }
val group = client.findGroup(id)
if (group != null) {
groups[group.cacheKey(inboxId)] = group
return group
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class GroupWrapper {
fun encodeToObj(client: Client, group: Group): Map<String, Any> {
return mapOf(
"clientAddress" to client.address,
"id" to group.id.toHex(),
"id" to group.id,
"createdAt" to group.createdAt.time,
"peerInboxIds" to group.peerInboxIds(),
"version" to "GROUP",
Expand Down
24 changes: 12 additions & 12 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ PODS:
- hermes-engine/Pre-built (= 0.71.14)
- hermes-engine/Pre-built (0.71.14)
- libevent (2.1.12)
- LibXMTP (0.5.4-beta2)
- LibXMTP (0.5.4-beta4)
- Logging (1.0.0)
- MessagePacker (0.4.7)
- MMKV (1.3.5):
- MMKVCore (~> 1.3.5)
- MMKVCore (1.3.5)
- MMKV (1.3.7):
- MMKVCore (~> 1.3.7)
- MMKVCore (1.3.7)
- OpenSSL-Universal (1.1.2200)
- RCT-Folly (2021.07.22.00):
- boost
Expand Down Expand Up @@ -449,16 +449,16 @@ PODS:
- GenericJSON (~> 2.0)
- Logging (~> 1.0.0)
- secp256k1.swift (~> 0.1)
- XMTP (0.13.3):
- XMTP (0.13.5):
- Connect-Swift (= 0.12.0)
- GzipSwift
- LibXMTP (= 0.5.4-beta2)
- LibXMTP (= 0.5.4-beta4)
- web3.swift
- XMTPReactNative (0.1.0):
- ExpoModulesCore
- MessagePacker
- secp256k1.swift
- XMTP (= 0.13.3)
- XMTP (= 0.13.5)
- Yoga (1.14.0)

DEPENDENCIES:
Expand Down Expand Up @@ -711,11 +711,11 @@ SPEC CHECKSUMS:
GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa
hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
LibXMTP: 16096f324c99d44712ed40876fe25150f694feab
LibXMTP: 97cafc8cdde820552c9960739397fef256b635fa
Logging: 9ef4ecb546ad3169398d5a723bc9bea1c46bef26
MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02
MMKV: 506311d0494023c2f7e0b62cc1f31b7370fa3cfb
MMKVCore: 9e2e5fd529b64a9fe15f1a7afb3d73b2e27b4db9
MMKV: 36a22a9ec84c9bb960613a089ddf6f48be9312b0
MMKVCore: 158e61c8516401a9fac730288acb29e6fc19bbf9
OpenSSL-Universal: 6e1ae0555546e604dbc632a2b9a24a9c46c41ef6
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCTRequired: e9df143e880d0e879e7a498dc06923d728809c79
Expand Down Expand Up @@ -763,8 +763,8 @@ SPEC CHECKSUMS:
secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634
SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1
web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959
XMTP: ca70dd11d709df02999325663e677fe775623d1e
XMTPReactNative: 1b79a6c8748387062ebcebe2c345f77f4998cae2
XMTP: 476b406c10c2c19183794b670790912545cc7699
XMTPReactNative: d172e052907d373f40348ed091cdbf7c6da4a331
Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9

PODFILE CHECKSUM: 95d6ace79946933ecf80684613842ee553dd76a2
Expand Down
96 changes: 96 additions & 0 deletions example/src/tests/groupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,102 @@ test('can message in a group', async () => {
return true
})

test('unpublished messages handling', async () => {
// Initialize fixture clients
const [alixClient, boClient] = await createClients(3)

// Create a new group with Bob and Alice
const boGroup = await boClient.conversations.newGroup([alixClient.address])

// Sync Alice's client to get the new group
await alixClient.conversations.syncGroups()
const alixGroup = await alixClient.conversations.findGroup(boGroup.id)
if (!alixGroup) {
throw new Error(`Group not found for id: ${boGroup.id}`)
}

// Check if the group is allowed initially
let isGroupAllowed = await alixClient.contacts.isGroupAllowed(boGroup.id)
if (isGroupAllowed) {
throw new Error('Group should not be allowed initially')
}

// Prepare a message in the group
const preparedMessageId = await alixGroup.prepareMessage('Test text')

// Check if the group is allowed after preparing the message
isGroupAllowed = await alixClient.contacts.isGroupAllowed(boGroup.id)
if (!isGroupAllowed) {
throw new Error('Group should be allowed after preparing a message')
}

// Verify the message count in the group
let messageCount = (await alixGroup.messages()).length
if (messageCount !== 1) {
throw new Error(`Message count should be 1, but it is ${messageCount}`)
}

// Verify the count of published and unpublished messages
let messageCountPublished = (
await alixGroup.messages({
deliveryStatus: MessageDeliveryStatus.PUBLISHED,
})
).length
let messageCountUnpublished = (
await alixGroup.messages({
deliveryStatus: MessageDeliveryStatus.UNPUBLISHED,
})
).length
if (messageCountPublished !== 0) {
throw new Error(
`Published message count should be 0, but it is ${messageCountPublished}`
)
}
if (messageCountUnpublished !== 1) {
throw new Error(
`Unpublished message count should be 1, but it is ${messageCountUnpublished}`
)
}

// Publish the prepared message
await alixGroup.publishPreparedMessages()

// Sync the group after publishing the message
await alixGroup.sync()

// Verify the message counts again
messageCountPublished = (
await alixGroup.messages({
deliveryStatus: MessageDeliveryStatus.PUBLISHED,
})
).length
messageCountUnpublished = (
await alixGroup.messages({
deliveryStatus: MessageDeliveryStatus.UNPUBLISHED,
})
).length
messageCount = (await alixGroup.messages()).length
if (messageCountPublished !== 1) {
throw new Error(
`Published message count should be 1, but it is ${messageCountPublished}`
)
}
if (messageCountUnpublished !== 0) {
throw new Error(
`Unpublished message count should be 0, but it is ${messageCountUnpublished}`
)
}
if (messageCount !== 1) {
throw new Error(`Message count should be 1, but it is ${messageCount}`)
}

// Retrieve all messages and verify the prepared message ID
const messages = await alixGroup.messages()
if (preparedMessageId !== messages[0].id) {
throw new Error(`Message ID should match the prepared message ID`)
}
})

test('can add members to a group', async () => {
// Create three MLS enabled Clients
const [alixClient, boClient, caroClient] = await createClients(3)
Expand Down
2 changes: 1 addition & 1 deletion ios/Wrappers/GroupWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ struct GroupWrapper {
static func encodeToObj(_ group: XMTP.Group, client: XMTP.Client) throws -> [String: Any] {
return [
"clientAddress": client.address,
"id": group.id.toHex,
"id": group.id,
"createdAt": UInt64(group.createdAt.timeIntervalSince1970 * 1000),
"peerInboxIds": try group.peerInboxIds,
"version": "GROUP",
Expand Down
Loading

0 comments on commit 90828ef

Please sign in to comment.