diff --git a/android/build.gradle b/android/build.gradle index e922dc08c..6dfab186e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -98,7 +98,7 @@ repositories { dependencies { implementation project(':expo-modules-core') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" - implementation "org.xmtp:android:0.10.4" + implementation "org.xmtp:android:0.10.5" 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" diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 2c5be7b18..2a37240d7 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -66,6 +66,7 @@ import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import com.facebook.common.util.Hex +import org.xmtp.android.library.messages.MessageDeliveryStatus import org.xmtp.android.library.messages.Topic import org.xmtp.android.library.push.Service @@ -527,7 +528,7 @@ class XMTPModule : Module() { } } - AsyncFunction("groupMessages") Coroutine { clientAddress: String, id: String, limit: Int?, before: Long?, after: Long?, direction: String? -> + AsyncFunction("groupMessages") Coroutine { clientAddress: String, id: String, limit: Int?, before: Long?, after: Long?, direction: String?, deliveryStatus: String? -> withContext(Dispatchers.IO) { logV("groupMessages") val client = clients[clientAddress] ?: throw XMTPException("No client") @@ -540,6 +541,9 @@ class XMTPModule : Module() { after = afterDate, direction = MessageApiOuterClass.SortDirection.valueOf( direction ?: "SORT_DIRECTION_DESCENDING" + ), + deliveryStatus = MessageDeliveryStatus.valueOf( + deliveryStatus ?: "ALL" ) )?.map { DecodedMessageWrapper.encode(it) } } diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DecodedMessageWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DecodedMessageWrapper.kt index 6b87624f7..ab871f023 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DecodedMessageWrapper.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DecodedMessageWrapper.kt @@ -24,7 +24,8 @@ class DecodedMessageWrapper { "content" to ContentJson(model.encodedContent).toJsonMap(), "senderAddress" to model.senderAddress, "sent" to model.sentAt.time, - "fallback" to fallback + "fallback" to fallback, + "deliveryStatus" to model.deliveryStatus.toString() ) } } diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 287ba6143..6d1408b5a 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -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.4.4-beta2) + - LibXMTP (0.4.4-beta3) - Logging (1.0.0) - MessagePacker (0.4.7) - MMKV (1.3.4): @@ -449,16 +449,16 @@ PODS: - GenericJSON (~> 2.0) - Logging (~> 1.0.0) - secp256k1.swift (~> 0.1) - - XMTP (0.10.3): + - XMTP (0.10.5): - Connect-Swift (= 0.12.0) - GzipSwift - - LibXMTP (= 0.4.4-beta2) + - LibXMTP (= 0.4.4-beta3) - web3.swift - XMTPReactNative (0.1.0): - ExpoModulesCore - MessagePacker - secp256k1.swift - - XMTP (= 0.10.3) + - XMTP (= 0.10.5) - Yoga (1.14.0) DEPENDENCIES: @@ -711,7 +711,7 @@ SPEC CHECKSUMS: GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - LibXMTP: 0c073613451e3850bfcaaab5438b481fe887cd97 + LibXMTP: 1422d36d715fe868b5800692f3d9b8a218a41e9d Logging: 9ef4ecb546ad3169398d5a723bc9bea1c46bef26 MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02 MMKV: ed58ad794b3f88c24d604a5b74f3fba17fcbaf74 @@ -763,8 +763,8 @@ SPEC CHECKSUMS: secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634 SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1 web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959 - XMTP: bf00ef58d4fbcc8ab740145a6303591adf3fb355 - XMTPReactNative: fae44562e5e457c66fde8e2613e1dfd3c1a71113 + XMTP: ec7fa7f9a2bd3990a7142c2f454e04c5b83b834a + XMTPReactNative: 42d61df711ce3c094d9fac9e3f44671f5466e93e Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9 PODFILE CHECKSUM: 95d6ace79946933ecf80684613842ee553dd76a2 diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index 5544f1c95..07d38cd4f 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -14,6 +14,8 @@ import { Group, ConversationContainer, ConversationVersion, + syncGroup, + MessageDeliveryStatus, } from '../../../src/index' export const groupTests: Test[] = [] @@ -205,11 +207,62 @@ test('production MLS V3 client creation throws error', async () => { ) }) +test('group message delivery status', async () => { + const [alixClient, boClient] = await createClients(2) + const alixGroup = await alixClient.conversations.newGroup([boClient.address]) + + await alixGroup.send('hello, world') + + const alixMessages: DecodedMessage[] = await alixGroup.messages(true) + + assert( + alixMessages.length === 2, + `the messages length should be 2 but was ${alixMessages.length}` + ) + + const alexMessagesFiltered: DecodedMessage[] = await alixGroup.messages( + true, + { + deliveryStatus: MessageDeliveryStatus.UNPUBLISHED, + } + ) + + assert( + alexMessagesFiltered.length === 1, + `the messages length should be 1 but was ${alexMessagesFiltered.length}` + ) + + const alixMessages2: DecodedMessage[] = await alixGroup.messages(false) + + assert( + alixMessages2.length === 2, + `the messages length should be 2 but was ${alixMessages.length}` + ) + + assert( + alixMessages2[0].deliveryStatus === 'PUBLISHED', + `the message should have a delivery status of PUBLISHED but was ${alixMessages2[0].deliveryStatus}` + ) + + const boGroup = (await boClient.conversations.listGroups())[0] + const boMessages: DecodedMessage[] = await boGroup.messages() + + assert( + boMessages.length === 1, + `the messages length should be 1 but was ${boMessages.length}` + ) + + assert( + boMessages[0].deliveryStatus === 'PUBLISHED', + `the message should have a delivery status of PUBLISHED but was ${boMessages[0].deliveryStatus}` + ) + + return true +}) + test('who added me to a group', async () => { const [alixClient, boClient] = await createClients(2) - const alixGroup = await alixClient.conversations.newGroup([ - boClient.address, - ]) + const alixGroup = await alixClient.conversations.newGroup([boClient.address]) const boGroup = (await boClient.conversations.listGroups())[0] const addedByAddress = await boGroup.addedByAddress() @@ -842,7 +895,9 @@ test('can paginate group messages', async () => { } await delayToPropogate() // bo can read messages from alix - const boMessages: DecodedMessage[] = await boGroups[0].messages(false, 1) + const boMessages: DecodedMessage[] = await boGroups[0].messages(false, { + limit: 1, + }) if (boMessages.length !== 1) { throw Error(`Should limit just 1 message but was ${boMessages.length}`) diff --git a/ios/Wrappers/DecodedMessageWrapper.swift b/ios/Wrappers/DecodedMessageWrapper.swift index b9780396d..8bc453bdd 100644 --- a/ios/Wrappers/DecodedMessageWrapper.swift +++ b/ios/Wrappers/DecodedMessageWrapper.swift @@ -16,6 +16,7 @@ struct DecodedMessageWrapper { "senderAddress": model.senderAddress, "sent": UInt64(model.sentAt.timeIntervalSince1970 * 1000), "fallback": fallback, + "deliveryStatus": model.deliveryStatus.rawValue.uppercased(), ] } diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index 01331862d..818b191e4 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -418,7 +418,7 @@ public class XMTPModule: Module { } } - AsyncFunction("groupMessages") { (clientAddress: String, id: String, limit: Int?, before: Double?, after: Double?, direction: String?) -> [String] in + AsyncFunction("groupMessages") { (clientAddress: String, id: String, limit: Int?, before: Double?, after: Double?, direction: String?, deliveryStatus: String?) -> [String] in guard let client = await clientsManager.getClient(key: clientAddress) else { throw Error.noClient } @@ -427,6 +427,8 @@ public class XMTPModule: Module { let afterDate = after != nil ? Date(timeIntervalSince1970: TimeInterval(after!) / 1000) : nil let sortDirection: Int = (direction != nil && direction == "SORT_DIRECTION_ASCENDING") ? 1 : 2 + + let status: String = (deliveryStatus != nil) ? deliveryStatus!.lowercased() : "all" guard let group = try await findGroup(clientAddress: clientAddress, id: id) else { throw Error.conversationNotFound("no group found for \(id)") @@ -435,7 +437,9 @@ public class XMTPModule: Module { before: beforeDate, after: afterDate, limit: limit, - direction: PagingInfoSortDirection(rawValue: sortDirection)) + direction: PagingInfoSortDirection(rawValue: sortDirection), + deliveryStatus: MessageDeliveryStatus(rawValue: status) + ) return decryptedMessages.compactMap { msg in do { diff --git a/ios/XMTPReactNative.podspec b/ios/XMTPReactNative.podspec index 0aa246e1c..814453097 100644 --- a/ios/XMTPReactNative.podspec +++ b/ios/XMTPReactNative.podspec @@ -26,5 +26,5 @@ Pod::Spec.new do |s| s.source_files = "**/*.{h,m,swift}" s.dependency 'secp256k1.swift' s.dependency "MessagePacker" - s.dependency "XMTP", "= 0.10.3" + s.dependency "XMTP", "= 0.10.5" end diff --git a/src/index.ts b/src/index.ts index 8cd7d959f..f07e0e770 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,7 @@ import { ConversationContainer, ConversationVersion, } from './lib/ConversationContainer' -import { DecodedMessage } from './lib/DecodedMessage' +import { DecodedMessage, MessageDeliveryStatus } from './lib/DecodedMessage' import { Group } from './lib/Group' import type { Query } from './lib/Query' import { ConversationSendPayload } from './lib/types' @@ -165,7 +165,8 @@ export async function groupMessages< direction?: | 'SORT_DIRECTION_ASCENDING' | 'SORT_DIRECTION_DESCENDING' - | undefined + | undefined, + deliveryStatus?: MessageDeliveryStatus | undefined ): Promise[]> { const messages = await XMTPModule.groupMessages( client.address, @@ -173,7 +174,8 @@ export async function groupMessages< limit, before, after, - direction + direction, + deliveryStatus ) return messages.map((json: string) => { return DecodedMessage.from(json, client) @@ -747,5 +749,5 @@ export { } from './lib/ConversationContainer' export { Query } from './lib/Query' export { XMTPPush } from './lib/XMTPPush' -export { ConsentListEntry, DecodedMessage } +export { ConsentListEntry, DecodedMessage, MessageDeliveryStatus } export { Group } from './lib/Group' diff --git a/src/lib/DecodedMessage.ts b/src/lib/DecodedMessage.ts index 244dcfb49..4eea4ad29 100644 --- a/src/lib/DecodedMessage.ts +++ b/src/lib/DecodedMessage.ts @@ -13,6 +13,13 @@ const allowEmptyProperties: (keyof NativeMessageContent)[] = [ 'text', 'readReceipt', ] +export enum MessageDeliveryStatus { + UNPUBLISHED = 'UNPUBLISHED', + PUBLISHED = 'PUBLISHED', + FAILED = 'FAILED', + ALL = 'ALL', +} + export class DecodedMessage< ContentTypes extends DefaultContentTypes = DefaultContentTypes, > { @@ -24,6 +31,7 @@ export class DecodedMessage< sent: number // timestamp in milliseconds nativeContent: NativeMessageContent fallback: string | undefined + deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.PUBLISHED static from( json: string, @@ -38,7 +46,8 @@ export class DecodedMessage< decoded.senderAddress, decoded.sent, decoded.content, - decoded.fallback + decoded.fallback, + decoded.deliveryStatus ) } @@ -53,6 +62,7 @@ export class DecodedMessage< sent: number // timestamp in milliseconds content: any fallback: string | undefined + deliveryStatus: MessageDeliveryStatus | undefined }, client: Client ): DecodedMessage { @@ -64,7 +74,8 @@ export class DecodedMessage< object.senderAddress, object.sent, object.content, - object.fallback + object.fallback, + object.deliveryStatus ) } @@ -76,7 +87,8 @@ export class DecodedMessage< senderAddress: string, sent: number, content: any, - fallback: string | undefined + fallback: string | undefined, + deliveryStatus: MessageDeliveryStatus = MessageDeliveryStatus.PUBLISHED ) { this.client = client this.id = id @@ -87,6 +99,7 @@ export class DecodedMessage< this.nativeContent = content // undefined comes back as null when bridged, ensure undefined so integrators don't have to add a new check for null as well this.fallback = fallback ?? undefined + this.deliveryStatus = deliveryStatus } content(): ExtractDecodedType<[...ContentTypes, TextCodec][number] | string> { diff --git a/src/lib/Group.ts b/src/lib/Group.ts index c34f172e8..be63c1ff1 100644 --- a/src/lib/Group.ts +++ b/src/lib/Group.ts @@ -2,10 +2,11 @@ import { ConversationVersion, ConversationContainer, } from './ConversationContainer' -import { DecodedMessage } from './DecodedMessage' +import { DecodedMessage, MessageDeliveryStatus } from './DecodedMessage' import { ConversationSendPayload } from './types/ConversationCodecs' import { DefaultContentTypes } from './types/DefaultContentType' import { EventTypes } from './types/EventTypes' +import { MessagesOptions } from './types/MessagesOptions' import { SendOptions } from './types/SendOptions' import * as XMTP from '../index' @@ -110,24 +111,20 @@ export class Group< */ async messages( skipSync: boolean = false, - limit?: number | undefined, - before?: number | Date | undefined, - after?: number | Date | undefined, - direction?: - | 'SORT_DIRECTION_ASCENDING' - | 'SORT_DIRECTION_DESCENDING' - | undefined + opts?: MessagesOptions ): Promise[]> { if (!skipSync) { await this.sync() } + return await XMTP.groupMessages( this.client, this.id, - limit, - before, - after, - direction + opts?.limit, + opts?.before, + opts?.after, + opts?.direction, + opts?.deliveryStatus ?? MessageDeliveryStatus.ALL ) } diff --git a/src/lib/types/MessagesOptions.ts b/src/lib/types/MessagesOptions.ts new file mode 100644 index 000000000..089d098d1 --- /dev/null +++ b/src/lib/types/MessagesOptions.ts @@ -0,0 +1,12 @@ +import { MessageDeliveryStatus } from '../DecodedMessage' + +export type MessagesOptions = { + limit?: number | undefined + before?: number | Date | undefined + after?: number | Date | undefined + direction?: + | 'SORT_DIRECTION_ASCENDING' + | 'SORT_DIRECTION_DESCENDING' + | undefined + deliveryStatus?: MessageDeliveryStatus | undefined +} diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index 1248d4ea3..ac9b03879 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -2,3 +2,4 @@ export * from './ContentCodec' export * from './SendOptions' export * from './ExtractDecodedType' export * from './ConversationCodecs' +export * from './MessagesOptions'