From f28d006ed2d15e9003eec7ba94453a5d3aa0756e Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 14 Feb 2025 16:21:47 -0800 Subject: [PATCH 01/20] start the android functions --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 72166377..1854aae7 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -1045,6 +1045,47 @@ class XMTPModule : Module() { } } + AsyncFunction("disappearingMessageSettings") Coroutine { installationId: String, conversationId: String -> + withContext(Dispatchers.IO) { + logV("disappearingMessageSettings") + val client = clients[installationId] ?: throw XMTPException("No client") + val conversation = client.findConversation(conversationId) + ?: throw XMTPException("no conversation found for $conversationId") + val settings = conversation.disapperingMessageSettings + DisapperingMessageSettingsWrapper.encode(client, settings) + } + } + + AsyncFunction("isDisappearingMessagesEnabled") Coroutine { installationId: String, conversationId: String -> + withContext(Dispatchers.IO) { + logV("isDisappearingMessagesEnabled") + val client = clients[installationId] ?: throw XMTPException("No client") + val conversation = client.findConversation(conversationId) + ?: throw XMTPException("no conversation found for $conversationId") + conversation.isDisappearingMessagesEnabled + } + } + + AsyncFunction("clearDisappearingMessageSettings") Coroutine { installationId: String, conversationId: String -> + withContext(Dispatchers.IO) { + logV("clearDisappearingMessageSettings") + val client = clients[installationId] ?: throw XMTPException("No client") + val conversation = client.findConversation(conversationId) + ?: throw XMTPException("no conversation found for $conversationId") + conversation.clearDisappearingMessageSettings() + } + } + + AsyncFunction("updateDisappearingMessageSettings") Coroutine { installationId: String, conversationId: String, startAtNs: Long, durationInNs: Long -> + withContext(Dispatchers.IO) { + logV("updateDisappearingMessageSettings") + val client = clients[installationId] ?: throw XMTPException("No client") + val conversation = client.findConversation(conversationId) + ?: throw XMTPException("no conversation found for $conversationId") + conversation.updateDisappearingMessageSettings(DisappearingMessageSettings(startAtNs, durationInNs)) + } + } + AsyncFunction("isGroupActive") Coroutine { installationId: String, groupId: String -> withContext(Dispatchers.IO) { logV("isGroupActive") From 05592eb39eeea6fe92a65d2a0350779b0d09380a Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 14 Feb 2025 16:24:27 -0800 Subject: [PATCH 02/20] Create silly-foxes-smell.md --- .changeset/silly-foxes-smell.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/silly-foxes-smell.md diff --git a/.changeset/silly-foxes-smell.md b/.changeset/silly-foxes-smell.md new file mode 100644 index 00000000..d19c05eb --- /dev/null +++ b/.changeset/silly-foxes-smell.md @@ -0,0 +1,9 @@ +--- +"@xmtp/react-native-sdk": patch +--- + +Disappearing Messages +DM membership adds (increases message length by 1 for dm creators) +Bug fix key package issues +Bug fix rate limiting +Mark addAccount as a delicate API From 71519b938eee0abd5f3ae20aa3ebc5d364d41203 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 14 Feb 2025 20:28:04 -0800 Subject: [PATCH 03/20] add warning for delicate apis --- src/lib/Client.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/lib/Client.ts b/src/lib/Client.ts index 1b6d8dd0..982a4782 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -355,9 +355,15 @@ export class Client< /** * Add this account to the current inboxId. + * Adding a wallet already associated with an inboxId will cause the wallet to lose access to that inbox. * @param {Signer} newAccount - The signer of the new account to be added. + * @param {boolean} allowReassignInboxId - The signer object used for authenticate the removal. */ - async addAccount(newAccount: Signer | WalletClient) { + async addAccount(newAccount: Signer | WalletClient, allowReassignInboxId: boolean = false) { + console.warn( + '⚠️ This function is delicate and should be used with caution. ' + + 'Adding a wallet already associated with an inboxId will cause the wallet to lose access to that inbox.' + ) const signer = getSigner(newAccount) if (!signer) { throw new Error('Signer is not configured') @@ -584,6 +590,10 @@ export class Client< * Drop the local database connection. This function is delicate and should be used with caution. App will error if database not properly reconnected. See: reconnectLocalDatabase() */ async dropLocalDatabaseConnection() { + console.warn( + '⚠️ This function is delicate and should be used with caution. ' + + 'App will error if database not properly reconnected. See: reconnectLocalDatabase()' + ) return await XMTPModule.dropLocalDatabaseConnection(this.installationId) } From 2524b21d03bad29f9ac43d4434fbb76031df3a9f Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sat, 15 Feb 2025 18:05:14 -0800 Subject: [PATCH 04/20] get the kotlin functions building --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 13 +++++++++--- .../DisappearingMessageSettingsWrapper.kt | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DisappearingMessageSettingsWrapper.kt diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 1854aae7..2b54d244 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -20,6 +20,7 @@ import expo.modules.xmtpreactnativesdk.wrappers.ConversationParamsWrapper import expo.modules.xmtpreactnativesdk.wrappers.CreateGroupParamsWrapper import expo.modules.xmtpreactnativesdk.wrappers.MessageWrapper import expo.modules.xmtpreactnativesdk.wrappers.DecryptedLocalAttachment +import expo.modules.xmtpreactnativesdk.wrappers.DisappearingMessageSettingsWrapper import expo.modules.xmtpreactnativesdk.wrappers.DmWrapper import expo.modules.xmtpreactnativesdk.wrappers.EncryptedLocalAttachment import expo.modules.xmtpreactnativesdk.wrappers.GroupWrapper @@ -55,6 +56,7 @@ import org.xmtp.android.library.codecs.EncryptedEncodedContent import org.xmtp.android.library.codecs.RemoteAttachment import org.xmtp.android.library.codecs.decoded import org.xmtp.android.library.hexToByteArray +import org.xmtp.android.library.libxmtp.DisappearingMessageSettings import org.xmtp.android.library.libxmtp.GroupPermissionPreconfiguration import org.xmtp.android.library.libxmtp.Message import org.xmtp.android.library.libxmtp.PermissionOption @@ -1051,8 +1053,8 @@ class XMTPModule : Module() { val client = clients[installationId] ?: throw XMTPException("No client") val conversation = client.findConversation(conversationId) ?: throw XMTPException("no conversation found for $conversationId") - val settings = conversation.disapperingMessageSettings - DisapperingMessageSettingsWrapper.encode(client, settings) + val settings = conversation.disappearingMessageSettings + DisappearingMessageSettingsWrapper.encode(client, settings) } } @@ -1082,7 +1084,12 @@ class XMTPModule : Module() { val client = clients[installationId] ?: throw XMTPException("No client") val conversation = client.findConversation(conversationId) ?: throw XMTPException("no conversation found for $conversationId") - conversation.updateDisappearingMessageSettings(DisappearingMessageSettings(startAtNs, durationInNs)) + conversation.updateDisappearingMessageSettings( + DisappearingMessageSettings( + startAtNs, + durationInNs + ) + ) } } diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DisappearingMessageSettingsWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DisappearingMessageSettingsWrapper.kt new file mode 100644 index 00000000..afdfe362 --- /dev/null +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/DisappearingMessageSettingsWrapper.kt @@ -0,0 +1,20 @@ +package expo.modules.xmtpreactnativesdk.wrappers + +import com.google.gson.GsonBuilder +import org.xmtp.android.library.libxmtp.DisappearingMessageSettings + +class DisappearingMessageSettingsWrapper { + + companion object { + fun encode(model: DisappearingMessageSettings): String { + val gson = GsonBuilder().create() + val message = encodeMap(model) + return gson.toJson(message) + } + + fun encodeMap(model: DisappearingMessageSettings): Map = mapOf( + "disappearStartingAtNs" to model.disappearStartingAtNs, + "retentionDurationInNs" to model.retentionDurationInNs, + ) + } +} \ No newline at end of file From 76be9da2118595111f5f161b86c007cfba0c0763 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sat, 15 Feb 2025 18:15:03 -0800 Subject: [PATCH 05/20] add the disappearing message setings --- .../modules/xmtpreactnativesdk/XMTPModule.kt | 24 +++++++++++++++---- .../wrappers/CreateGroupParamsWrapper.kt | 8 +++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 2b54d244..71fc4f90 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -785,20 +785,30 @@ class XMTPModule : Module() { } } - AsyncFunction("findOrCreateDm") Coroutine { installationId: String, peerAddress: String -> + AsyncFunction("findOrCreateDm") Coroutine { installationId: String, peerAddress: String, disappearStartingAtNs: Long?, retentionDurationInNs: Long? -> withContext(Dispatchers.IO) { logV("findOrCreateDm") val client = clients[installationId] ?: throw XMTPException("No client") - val dm = client.conversations.findOrCreateDm(peerAddress) + val settings = if (disappearStartingAtNs != null && retentionDurationInNs != null) { + DisappearingMessageSettings(disappearStartingAtNs, retentionDurationInNs) + } else { + null + } + val dm = client.conversations.findOrCreateDm(peerAddress, settings) DmWrapper.encode(client, dm) } } - AsyncFunction("findOrCreateDmWithInboxId") Coroutine { installationId: String, peerInboxId: String -> + AsyncFunction("findOrCreateDmWithInboxId") Coroutine { installationId: String, peerInboxId: String, disappearStartingAtNs: Long?, retentionDurationInNs: Long? -> withContext(Dispatchers.IO) { logV("findOrCreateDmWithInboxId") val client = clients[installationId] ?: throw XMTPException("No client") - val dm = client.conversations.findOrCreateDmWithInboxId(peerInboxId) + val settings = if (disappearStartingAtNs != null && retentionDurationInNs != null) { + DisappearingMessageSettings(disappearStartingAtNs, retentionDurationInNs) + } else { + null + } + val dm = client.conversations.findOrCreateDmWithInboxId(peerInboxId, settings) DmWrapper.encode(client, dm) } } @@ -819,6 +829,7 @@ class XMTPModule : Module() { createGroupParams.groupName, createGroupParams.groupImageUrlSquare, createGroupParams.groupDescription, + createGroupParams.disappearingMessageSettings ) GroupWrapper.encode(client, group) } @@ -840,6 +851,7 @@ class XMTPModule : Module() { createGroupParams.groupName, createGroupParams.groupImageUrlSquare, createGroupParams.groupDescription, + createGroupParams.disappearingMessageSettings ) GroupWrapper.encode(client, group) } @@ -861,6 +873,7 @@ class XMTPModule : Module() { createGroupParams.groupName, createGroupParams.groupImageUrlSquare, createGroupParams.groupDescription, + createGroupParams.disappearingMessageSettings ) GroupWrapper.encode(client, group) } @@ -882,6 +895,7 @@ class XMTPModule : Module() { createGroupParams.groupName, createGroupParams.groupImageUrlSquare, createGroupParams.groupDescription, + createGroupParams.disappearingMessageSettings ) GroupWrapper.encode(client, group) } @@ -1054,7 +1068,7 @@ class XMTPModule : Module() { val conversation = client.findConversation(conversationId) ?: throw XMTPException("no conversation found for $conversationId") val settings = conversation.disappearingMessageSettings - DisappearingMessageSettingsWrapper.encode(client, settings) + settings?.let { DisappearingMessageSettingsWrapper.encode(it) } } } diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/CreateGroupParamsWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/CreateGroupParamsWrapper.kt index b330f4ea..41c3c0c8 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/CreateGroupParamsWrapper.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/CreateGroupParamsWrapper.kt @@ -1,19 +1,27 @@ package expo.modules.xmtpreactnativesdk.wrappers import com.google.gson.JsonParser +import org.xmtp.android.library.libxmtp.DisappearingMessageSettings class CreateGroupParamsWrapper( val groupName: String, val groupImageUrlSquare: String, val groupDescription: String, + val disappearingMessageSettings: DisappearingMessageSettings, ) { companion object { fun createGroupParamsFromJson(authParams: String): CreateGroupParamsWrapper { val jsonOptions = JsonParser.parseString(authParams).asJsonObject + val settings = DisappearingMessageSettings( + if (jsonOptions.has("disappearStartingAtNs")) jsonOptions.get("disappearStartingAtNs").asLong else 0, + if (jsonOptions.has("retentionDurationInNs")) jsonOptions.get("retentionDurationInNs").asLong else 0 + ) + return CreateGroupParamsWrapper( if (jsonOptions.has("name")) jsonOptions.get("name").asString else "", if (jsonOptions.has("imageUrlSquare")) jsonOptions.get("imageUrlSquare").asString else "", if (jsonOptions.has("description")) jsonOptions.get("description").asString else "", + settings ) } } From acc932ddb5fa068774b5ad50fd73548177f8ce0b Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sat, 15 Feb 2025 18:20:27 -0800 Subject: [PATCH 06/20] do the swift side --- .../DisappearingMessageSettingsWrapper.swift | 24 ++++ ios/XMTPModule.swift | 119 +++++++++++++++--- 2 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 ios/Wrappers/DisappearingMessageSettingsWrapper.swift diff --git a/ios/Wrappers/DisappearingMessageSettingsWrapper.swift b/ios/Wrappers/DisappearingMessageSettingsWrapper.swift new file mode 100644 index 00000000..c30cbd69 --- /dev/null +++ b/ios/Wrappers/DisappearingMessageSettingsWrapper.swift @@ -0,0 +1,24 @@ +import Foundation +import XMTP + +struct DisappearingMessageSettingsWrapper { + static func encodeToObj(_ settings: XMTP.DisappearingMessageSettings) throws + -> [String: Any] + { + return [ + "disappearStartingAtNs": settings.disappearStartingAtNs, + "retentionDurationInNs": settings.retentionDurationInNs, + ] + } + + static func encode(_ entry: XMTP.DisappearingMessageSettings) throws + -> String + { + let obj = try encodeToObj(entry) + let data = try JSONSerialization.data(withJSONObject: obj) + guard let result = String(data: data, encoding: .utf8) else { + throw WrapperError.encodeError("could not encode expirations") + } + return result + } +} diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index 8bd3188d..6820864b 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -658,27 +658,36 @@ public class XMTPModule: Module { } AsyncFunction("conversationMessagesWithReactions") { - ( - installationId: String, conversationId: String, limit: Int?, - beforeNs: Double?, afterNs: Double?, direction: String? - ) -> [String] in - guard let client = await clientsManager.getClient(key: installationId) else { + ( + installationId: String, conversationId: String, limit: Int?, + beforeNs: Double?, afterNs: Double?, direction: String? + ) -> [String] in + guard + let client = await clientsManager.getClient(key: installationId) + else { throw Error.noClient } - guard let conversation = try await client.findConversation(conversationId: conversationId) else { - throw Error.conversationNotFound("no conversation found for \(conversationId)") + guard + let conversation = try await client.findConversation( + conversationId: conversationId) + else { + throw Error.conversationNotFound( + "no conversation found for \(conversationId)") } let messages = try await conversation.messagesWithReactions( limit: limit, beforeNs: beforeNs != nil ? Int64(beforeNs!) : nil, afterNs: afterNs != nil ? Int64(afterNs!) : nil, - direction: getSortDirection(direction: direction ?? "DESCENDING") + direction: getSortDirection( + direction: direction ?? "DESCENDING") ) return messages.compactMap { msg in do { return try MessageWrapper.encode(msg) } catch { - print("discarding message, unable to encode wrapper \(msg.id)") + print( + "discarding message, unable to encode wrapper \(msg.id)" + ) return nil } } @@ -904,7 +913,7 @@ public class XMTPModule: Module { throw error } } - + AsyncFunction("findOrCreateDmWithInboxId") { (installationId: String, peerInboxId: String) -> String in guard @@ -914,8 +923,9 @@ public class XMTPModule: Module { } do { - let dm = try await client.conversations.findOrCreateDmWithInboxId( - with: peerInboxId) + let dm = try await client.conversations + .findOrCreateDmWithInboxId( + with: peerInboxId) return try await DmWrapper.encode(dm, client: client) } catch { print("ERRRO!: \(error.localizedDescription)") @@ -991,7 +1001,7 @@ public class XMTPModule: Module { throw error } } - + AsyncFunction("createGroupWithInboxIds") { ( installationId: String, inboxIds: [String], @@ -1133,7 +1143,8 @@ public class XMTPModule: Module { } AsyncFunction("syncAllConversations") { - (installationId: String, consentStringStates: [String]?) -> UInt32 in + (installationId: String, consentStringStates: [String]?) -> UInt32 + in guard let client = await clientsManager.getClient(key: installationId) else { @@ -1143,7 +1154,7 @@ public class XMTPModule: Module { if let states = consentStringStates { consentStates = try getConsentStates(states: states) } else { - consentStates = nil + consentStates = nil } return try await client.conversations.syncAllConversations( consentStates: consentStates) @@ -1315,6 +1326,84 @@ public class XMTPModule: Module { groupDescription: description) } + AsyncFunction("disappearingMessageSettings") { + (installationId: String, conversationId: String) async throws + -> DisappearingMessageSettingsWrapper? in + guard + let client = await clientsManager.getClient(key: installationId) + else { + throw Error.noClient + } + guard + let conversation = try client.findConversation( + conversationId: conversationId) + else { + throw Error.conversationNotFound( + "No conversation found for \(conversationId)") + } + return conversation.disappearingMessageSettings.map { + DisappearingMessageSettingsWrapper.encode($0) + } + } + + AsyncFunction("isDisappearingMessagesEnabled") { + (installationId: String, conversationId: String) async throws + -> Bool in + guard + let client = await clientsManager.getClient(key: installationId) + else { + throw Error.noClient + } + guard + let conversation = try client.findConversation( + conversationId: conversationId) + else { + throw Error.conversationNotFound( + "No conversation found for \(conversationId)") + } + return conversation.isDisappearingMessagesEnabled + } + + AsyncFunction("clearDisappearingMessageSettings") { + (installationId: String, conversationId: String) async throws in + guard + let client = await clientsManager.getClient(key: installationId) + else { + throw Error.noClient + } + guard + let conversation = try client.findConversation( + conversationId: conversationId) + else { + throw Error.conversationNotFound( + "No conversation found for \(conversationId)") + } + try await conversation.clearDisappearingMessageSettings() + } + + AsyncFunction("updateDisappearingMessageSettings") { + ( + installationId: String, conversationId: String, + startAtNs: Int64, durationInNs: Int64 + ) async throws in + guard + let client = await clientsManager.getClient(key: installationId) + else { + throw Error.noClient + } + guard + let conversation = try client.findConversation( + conversationId: conversationId) + else { + throw Error.conversationNotFound( + "No conversation found for \(conversationId)") + } + try await conversation.updateDisappearingMessageSettings( + DisappearingMessageSettings( + startAtNs: startAtNs, durationInNs: durationInNs) + ) + } + AsyncFunction("isGroupActive") { (installationId: String, id: String) -> Bool in guard From 3500dc197c8d3172d949d14a4d4af597b8530f07 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 16 Feb 2025 15:02:58 -0800 Subject: [PATCH 07/20] update lock file --- example/ios/Podfile.lock | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index af67aa22..a1039022 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -55,7 +55,7 @@ PODS: - hermes-engine/Pre-built (= 0.71.14) - hermes-engine/Pre-built (0.71.14) - libevent (2.1.12) - - LibXMTP (3.0.26) + - LibXMTP (3.0.27) - MessagePacker (0.4.7) - MMKV (2.0.2): - MMKVCore (~> 2.0.2) @@ -452,7 +452,7 @@ PODS: - Connect-Swift (= 1.0.0) - CryptoSwift (= 1.8.3) - CSecp256k1 (~> 0.2) - - LibXMTP (= 3.0.26) + - LibXMTP (= 3.0.27) - SQLCipher (= 4.5.7) - XMTPReactNative (3.1.12): - CSecp256k1 (~> 0.2) @@ -530,7 +530,6 @@ DEPENDENCIES: - RNFS (from `../node_modules/react-native-fs`) - RNScreens (from `../node_modules/react-native-screens`) - RNSVG (from `../node_modules/react-native-svg`) - - XMTP (from `https://github.com/xmtp/xmtp-ios.git`, commit `77940ee4790390154248d0fed0a2d5316fd99b3b`) - XMTPReactNative (from `../../ios`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) @@ -549,6 +548,7 @@ SPEC REPOS: - OpenSSL-Universal - SQLCipher - SwiftProtobuf + - XMTP EXTERNAL SOURCES: boost: @@ -679,21 +679,13 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-screens" RNSVG: :path: "../node_modules/react-native-svg" - XMTP: - :commit: 77940ee4790390154248d0fed0a2d5316fd99b3b - :git: https://github.com/xmtp/xmtp-ios.git XMTPReactNative: :path: "../../ios" Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" -CHECKOUT OPTIONS: - XMTP: - :commit: 77940ee4790390154248d0fed0a2d5316fd99b3b - :git: https://github.com/xmtp/xmtp-ios.git - SPEC CHECKSUMS: - boost: 7dcd2de282d72e344012f7d6564d024930a6a440 + boost: 57d2868c099736d80fcd648bf211b4431e51a558 CoinbaseWalletSDK: ea1f37512bbc69ebe07416e3b29bf840f5cc3152 CoinbaseWalletSDKExpo: c79420eb009f482f768c23b6768fc5b2d7c98777 Connect-Swift: 84e043b904f63dc93a2c01c6c125da25e765b50d @@ -719,7 +711,7 @@ SPEC CHECKSUMS: glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - LibXMTP: e80c8c226e67d8c820e81c5a2bfa1934ab5d263c + LibXMTP: 312922ac85f2b20983ba14beb08722b08002ded4 MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02 MMKV: 3eacda84cd1c4fc95cf848d3ecb69d85ed56006c MMKVCore: 508b4d3a8ce031f1b5c8bd235f0517fb3f4c73a9 @@ -770,10 +762,10 @@ SPEC CHECKSUMS: RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396 SQLCipher: 5e6bfb47323635c8b657b1b27d25c5f1baf63bf5 SwiftProtobuf: 4dbaffec76a39a8dc5da23b40af1a5dc01a4c02d - XMTP: 5815a5886b5a698e910d7eb49c11681e9001c931 + XMTP: 12cc7f309b993790a2a6c2a8bb2048c62fb4b788 XMTPReactNative: 68152197c8135a6e0fe03637e7cde6bdd896d75c Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9 -PODFILE CHECKSUM: bb988e1a087fa2d3c0bc34c187128b2c7c6b2e58 +PODFILE CHECKSUM: 2d04c11c2661aeaad852cd3ada0b0f1b06e0cf24 COCOAPODS: 1.15.2 From c048cc96c7d065c595e0d8ab52840b0d16112cb5 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 16 Feb 2025 15:12:59 -0800 Subject: [PATCH 08/20] fix up the swift code --- ios/XMTPModule.swift | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index 6820864b..f562fb26 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -1327,52 +1327,50 @@ public class XMTPModule: Module { } AsyncFunction("disappearingMessageSettings") { - (installationId: String, conversationId: String) async throws - -> DisappearingMessageSettingsWrapper? in + (installationId: String, conversationId: String) -> String? in guard let client = await clientsManager.getClient(key: installationId) else { throw Error.noClient } guard - let conversation = try client.findConversation( + let conversation = try await client.findConversation( conversationId: conversationId) else { throw Error.conversationNotFound( "No conversation found for \(conversationId)") } - return conversation.disappearingMessageSettings.map { - DisappearingMessageSettingsWrapper.encode($0) + return try conversation.disappearingMessageSettings.map { + try DisappearingMessageSettingsWrapper.encode($0) } } AsyncFunction("isDisappearingMessagesEnabled") { - (installationId: String, conversationId: String) async throws - -> Bool in + (installationId: String, conversationId: String) -> Bool in guard let client = await clientsManager.getClient(key: installationId) else { throw Error.noClient } guard - let conversation = try client.findConversation( + let conversation = try await client.findConversation( conversationId: conversationId) else { throw Error.conversationNotFound( "No conversation found for \(conversationId)") } - return conversation.isDisappearingMessagesEnabled + return try conversation.isDisappearingMessagesEnabled() } AsyncFunction("clearDisappearingMessageSettings") { - (installationId: String, conversationId: String) async throws in + (installationId: String, conversationId: String) in guard let client = await clientsManager.getClient(key: installationId) else { throw Error.noClient } guard - let conversation = try client.findConversation( + let conversation = try await client.findConversation( conversationId: conversationId) else { throw Error.conversationNotFound( @@ -1385,14 +1383,14 @@ public class XMTPModule: Module { ( installationId: String, conversationId: String, startAtNs: Int64, durationInNs: Int64 - ) async throws in + ) in guard let client = await clientsManager.getClient(key: installationId) else { throw Error.noClient } guard - let conversation = try client.findConversation( + let conversation = try await client.findConversation( conversationId: conversationId) else { throw Error.conversationNotFound( @@ -1400,7 +1398,8 @@ public class XMTPModule: Module { } try await conversation.updateDisappearingMessageSettings( DisappearingMessageSettings( - startAtNs: startAtNs, durationInNs: durationInNs) + disappearStartingAtNs: startAtNs, + retentionDurationInNs: durationInNs) ) } From 2ed7cb29be7354ee084c7ea7c2ae3bbb0c2b73f0 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 16 Feb 2025 15:18:35 -0800 Subject: [PATCH 09/20] add swift create functions --- ios/Wrappers/CreateGroupParamsWrapper.swift | 48 +++++++++++++-------- ios/XMTPModule.swift | 41 ++++++++++++++---- 2 files changed, 64 insertions(+), 25 deletions(-) diff --git a/ios/Wrappers/CreateGroupParamsWrapper.swift b/ios/Wrappers/CreateGroupParamsWrapper.swift index e4c36ac1..6a6fbda7 100644 --- a/ios/Wrappers/CreateGroupParamsWrapper.swift +++ b/ios/Wrappers/CreateGroupParamsWrapper.swift @@ -1,22 +1,36 @@ import Foundation +import XMTP struct CreateGroupParamsWrapper { - let groupName: String - let groupImageUrlSquare: String - let groupDescription: String + let groupName: String + let groupImageUrlSquare: String + let groupDescription: String + let disappearingMessageSettings: DisappearingMessageSettings - static func createGroupParamsFromJson(_ authParams: String) -> CreateGroupParamsWrapper { - let data = authParams.data(using: .utf8) ?? Data() - let jsonOptions = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] ?? [:] - - let groupName = jsonOptions["name"] as? String ?? "" - let groupImageUrlSquare = jsonOptions["imageUrlSquare"] as? String ?? "" - let groupDescription = jsonOptions["description"] as? String ?? "" - - return CreateGroupParamsWrapper( - groupName: groupName, - groupImageUrlSquare: groupImageUrlSquare, - groupDescription: groupDescription - ) - } + static func createGroupParamsFromJson(_ authParams: String) + -> CreateGroupParamsWrapper + { + let data = authParams.data(using: .utf8) ?? Data() + let jsonOptions = + (try? JSONSerialization.jsonObject(with: data, options: [])) + as? [String: Any] ?? [:] + + let settings = DisappearingMessageSettings( + disappearStartingAtNs: jsonOptions["disappearStartingAtNs"] + as? Int64 ?? 0, + retentionDurationInNs: jsonOptions["retentionDurationInNs"] + as? Int64 ?? 0 + ) + + let groupName = jsonOptions["name"] as? String ?? "" + let groupImageUrlSquare = jsonOptions["imageUrlSquare"] as? String ?? "" + let groupDescription = jsonOptions["description"] as? String ?? "" + + return CreateGroupParamsWrapper( + groupName: groupName, + groupImageUrlSquare: groupImageUrlSquare, + groupDescription: groupDescription, + disappearingMessageSettings: settings + ) + } } diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index f562fb26..5e093ded 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -897,16 +897,24 @@ public class XMTPModule: Module { } AsyncFunction("findOrCreateDm") { - (installationId: String, peerAddress: String) -> String in + ( + installationId: String, peerAddress: String, + disappearStartingAtNs: Int64?, retentionDurationInNs: Int64? + ) -> String in guard let client = await clientsManager.getClient(key: installationId) else { throw Error.noClient } + let settings = + (disappearStartingAtNs != nil && retentionDurationInNs != nil) + ? DisappearingMessageSettings( + disappearStartingAtNs: disappearStartingAtNs!, + retentionDurationInNs: retentionDurationInNs!) : nil do { let dm = try await client.conversations.findOrCreateDm( - with: peerAddress) + with: peerAddress, disappearingMessageSettings: settings) return try await DmWrapper.encode(dm, client: client) } catch { print("ERRRO!: \(error.localizedDescription)") @@ -915,17 +923,26 @@ public class XMTPModule: Module { } AsyncFunction("findOrCreateDmWithInboxId") { - (installationId: String, peerInboxId: String) -> String in + ( + installationId: String, peerInboxId: String, + disappearStartingAtNs: Int64?, retentionDurationInNs: Int64? + ) -> String in guard let client = await clientsManager.getClient(key: installationId) else { throw Error.noClient } + let settings = + (disappearStartingAtNs != nil && retentionDurationInNs != nil) + ? DisappearingMessageSettings( + disappearStartingAtNs: disappearStartingAtNs!, + retentionDurationInNs: retentionDurationInNs!) : nil do { let dm = try await client.conversations .findOrCreateDmWithInboxId( - with: peerInboxId) + with: peerInboxId, disappearingMessageSettings: settings + ) return try await DmWrapper.encode(dm, client: client) } catch { print("ERRRO!: \(error.localizedDescription)") @@ -961,7 +978,9 @@ public class XMTPModule: Module { permissions: permissionLevel, name: createGroupParams.groupName, imageUrlSquare: createGroupParams.groupImageUrlSquare, - description: createGroupParams.groupDescription + description: createGroupParams.groupDescription, + disappearingMessageSettings: createGroupParams + .disappearingMessageSettings ) return try await GroupWrapper.encode(group, client: client) } catch { @@ -993,7 +1012,9 @@ public class XMTPModule: Module { permissionPolicySet: permissionPolicySet, name: createGroupParams.groupName, imageUrlSquare: createGroupParams.groupImageUrlSquare, - description: createGroupParams.groupDescription + description: createGroupParams.groupDescription, + disappearingMessageSettings: createGroupParams + .disappearingMessageSettings ) return try await GroupWrapper.encode(group, client: client) } catch { @@ -1030,7 +1051,9 @@ public class XMTPModule: Module { permissions: permissionLevel, name: createGroupParams.groupName, imageUrlSquare: createGroupParams.groupImageUrlSquare, - description: createGroupParams.groupDescription + description: createGroupParams.groupDescription, + disappearingMessageSettings: createGroupParams + .disappearingMessageSettings ) return try await GroupWrapper.encode(group, client: client) } catch { @@ -1062,7 +1085,9 @@ public class XMTPModule: Module { permissionPolicySet: permissionPolicySet, name: createGroupParams.groupName, imageUrlSquare: createGroupParams.groupImageUrlSquare, - description: createGroupParams.groupDescription + description: createGroupParams.groupDescription, + disappearingMessageSettings: createGroupParams + .disappearingMessageSettings ) return try await GroupWrapper.encode(group, client: client) } catch { From ee0343077c55737cdb6b185c0d303292aa388cd7 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 16 Feb 2025 15:26:33 -0800 Subject: [PATCH 10/20] add allowReassignInboxId to addAccount --- .../java/expo/modules/xmtpreactnativesdk/XMTPModule.kt | 4 ++-- ios/XMTPModule.swift | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 71fc4f90..5a7a057c 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -389,7 +389,7 @@ class XMTPModule : Module() { } } - AsyncFunction("addAccount") Coroutine { installationId: String, newAddress: String, walletParams: String -> + AsyncFunction("addAccount") Coroutine { installationId: String, newAddress: String, walletParams: String, allowReassignInboxId: Boolean -> withContext(Dispatchers.IO) { logV("addAccount") val client = clients[installationId] ?: throw XMTPException("No client") @@ -404,7 +404,7 @@ class XMTPModule : Module() { ) signer = reactSigner - client.addAccount(reactSigner) + client.addAccount(reactSigner, allowReassignInboxId) signer = null } } diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index 5e093ded..eb7f4518 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -341,7 +341,10 @@ public class XMTPModule: Module { } AsyncFunction("addAccount") { - (installationId: String, newAddress: String, walletParams: String) + ( + installationId: String, newAddress: String, + walletParams: String, allowReassignInboxId: Bool + ) in guard let client = await clientsManager.getClient(key: installationId) @@ -357,7 +360,8 @@ public class XMTPModule: Module { blockNumber: walletOptions.blockNumber) self.signer = signer - try await client.addAccount(newAccount: signer) + try await client.addAccount( + newAccount: signer, allowReassignInboxId: allowReassignInboxId) self.signer = nil } From cf33e057ba5822964b65b626d35abeb9259b7fe4 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 16 Feb 2025 15:29:48 -0800 Subject: [PATCH 11/20] add account index functions --- src/index.ts | 6 ++++-- src/lib/Client.ts | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2ac6f54c..fdb6daf6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -236,7 +236,8 @@ export async function addAccount( newAddress: Address, walletType?: WalletType | undefined, chainId?: number | undefined, - blockNumber?: number | undefined + blockNumber?: number | undefined, + allowReassignInboxId: boolean = false ) { const walletParams: WalletParams = { walletType, @@ -246,7 +247,8 @@ export async function addAccount( return XMTPModule.addAccount( installationId, newAddress, - JSON.stringify(walletParams) + JSON.stringify(walletParams), + allowReassignInboxId ) } diff --git a/src/lib/Client.ts b/src/lib/Client.ts index 982a4782..a5f5f17b 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -391,7 +391,8 @@ export class Client< await signer.getAddress(), signer.walletType?.(), signer.getChainId?.(), - signer.getBlockNumber?.() + signer.getBlockNumber?.(), + allowReassignInboxId ) Client.signSubscription?.remove() resolve() From 883d4e224f4e47ca8c8272c9e091a4e194bf7f0f Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 16 Feb 2025 15:49:59 -0800 Subject: [PATCH 12/20] add the create functions --- src/index.ts | 45 ++++++++++++++++++----- src/lib/Conversations.ts | 56 +++++++++++++++++++++++------ src/lib/types/CreateGroupOptions.ts | 6 ++++ 3 files changed, 88 insertions(+), 19 deletions(-) diff --git a/src/index.ts b/src/index.ts index fdb6daf6..5e2ae833 100644 --- a/src/index.ts +++ b/src/index.ts @@ -684,10 +684,17 @@ export async function findOrCreateDm< ContentTypes extends DefaultContentTypes = DefaultContentTypes, >( client: Client, - peerAddress: Address + peerAddress: Address, + disappearStartingAtNs: number | undefined, + retentionDurationInNs: number | undefined ): Promise> { const dm = JSON.parse( - await XMTPModule.findOrCreateDm(client.installationId, peerAddress) + await XMTPModule.findOrCreateDm( + client.installationId, + peerAddress, + disappearStartingAtNs, + retentionDurationInNs + ) ) return new Dm(client, dm) } @@ -696,12 +703,16 @@ export async function findOrCreateDmWithInboxId< ContentTypes extends DefaultContentTypes = DefaultContentTypes, >( client: Client, - peerInboxId: InboxId + peerInboxId: InboxId, + disappearStartingAtNs: number | undefined, + retentionDurationInNs: number | undefined ): Promise> { const dm = JSON.parse( await XMTPModule.findOrCreateDmWithInboxId( client.installationId, - peerInboxId + peerInboxId, + disappearStartingAtNs, + retentionDurationInNs ) ) return new Dm(client, dm) @@ -715,12 +726,16 @@ export async function createGroup< permissionLevel: 'all_members' | 'admin_only' = 'all_members', name: string = '', imageUrlSquare: string = '', - description: string = '' + description: string = '', + disappearStartingAtNs: number = 0, + retentionDurationInNs: number = 0 ): Promise> { const options: CreateGroupParams = { name, imageUrlSquare, description, + disappearStartingAtNs, + retentionDurationInNs, } const group = JSON.parse( await XMTPModule.createGroup( @@ -742,12 +757,16 @@ export async function createGroupCustomPermissionsWithInboxIds< permissionPolicySet: PermissionPolicySet, name: string = '', imageUrlSquare: string = '', - description: string = '' + description: string = '', + disappearStartingAtNs: number = 0, + retentionDurationInNs: number = 0 ): Promise> { const options: CreateGroupParams = { name, imageUrlSquare, description, + disappearStartingAtNs, + retentionDurationInNs, } const group = JSON.parse( await XMTPModule.createGroupCustomPermissionsWithInboxIds( @@ -769,12 +788,16 @@ export async function createGroupWithInboxIds< permissionLevel: 'all_members' | 'admin_only' = 'all_members', name: string = '', imageUrlSquare: string = '', - description: string = '' + description: string = '', + disappearStartingAtNs: number = 0, + retentionDurationInNs: number = 0 ): Promise> { const options: CreateGroupParams = { name, imageUrlSquare, description, + disappearStartingAtNs, + retentionDurationInNs, } const group = JSON.parse( await XMTPModule.createGroupWithInboxIds( @@ -796,12 +819,16 @@ export async function createGroupCustomPermissions< permissionPolicySet: PermissionPolicySet, name: string = '', imageUrlSquare: string = '', - description: string = '' + description: string = '', + disappearStartingAtNs: number = 0, + retentionDurationInNs: number = 0 ): Promise> { const options: CreateGroupParams = { name, imageUrlSquare, description, + disappearStartingAtNs, + retentionDurationInNs, } const group = JSON.parse( await XMTPModule.createGroupCustomPermissions( @@ -1299,6 +1326,8 @@ interface CreateGroupParams { name: string imageUrlSquare: string description: string + disappearStartingAtNs: number + retentionDurationInNs: number } export { Client } from './lib/Client' diff --git a/src/lib/Conversations.ts b/src/lib/Conversations.ts index fd748dfe..335df35d 100644 --- a/src/lib/Conversations.ts +++ b/src/lib/Conversations.ts @@ -6,7 +6,10 @@ import { DecodedMessage } from './DecodedMessage' import { Dm, DmParams } from './Dm' import { Group, GroupParams } from './Group' import { ConversationOptions } from './types/ConversationOptions' -import { CreateGroupOptions } from './types/CreateGroupOptions' +import { + CreateGroupOptions, + DisappearingMessageSettings, +} from './types/CreateGroupOptions' import { DecodedMessageUnion } from './types/DecodedMessageUnion' import { DefaultContentTypes } from './types/DefaultContentType' import { EventTypes } from './types/EventTypes' @@ -125,13 +128,20 @@ export default class Conversations< * This method creates a new conversation with the specified peer address and context. * * @param {Address} peerAddress - The address of the peer to create a conversation with. + * @param {DisappearingMessageSettings} disappearingMessageSettings - The disappearing message settings for this dm or undefined. * @returns {Promise} A Promise that resolves to a Conversation object. */ async newConversation( - peerAddress: Address + peerAddress: Address, + disappearingMessageSettings?: DisappearingMessageSettings | undefined ): Promise> { const checksumAddress = getAddress(peerAddress) - return await XMTPModule.findOrCreateDm(this.client, checksumAddress) + return await XMTPModule.findOrCreateDm( + this.client, + checksumAddress, + disappearingMessageSettings?.disappearStartingAtNs, + disappearingMessageSettings?.retentionDurationInNs + ) } /** @@ -140,10 +150,19 @@ export default class Conversations< * This method creates a new conversation with the specified peer address. * * @param {Address} peerAddress - The address of the peer to create a conversation with. + * @param {DisappearingMessageSettings} disappearingMessageSettings - The disappearing message settings for this dm or undefined. * @returns {Promise} A Promise that resolves to a Dm object. */ - async findOrCreateDm(peerAddress: Address): Promise> { - return await XMTPModule.findOrCreateDm(this.client, peerAddress) + async findOrCreateDm( + peerAddress: Address, + disappearingMessageSettings?: DisappearingMessageSettings | undefined + ): Promise> { + return await XMTPModule.findOrCreateDm( + this.client, + peerAddress, + disappearingMessageSettings?.disappearStartingAtNs, + disappearingMessageSettings?.retentionDurationInNs + ) } /** @@ -152,12 +171,19 @@ export default class Conversations< * This method creates a new conversation with the specified peer inboxId. * * @param {InboxId} peerInboxId - The inboxId of the peer to create a conversation with. + * @param {DisappearingMessageSettings} disappearingMessageSettings - The disappearing message settings for this dm or undefined. * @returns {Promise} A Promise that resolves to a Dm object. */ async findOrCreateDmWithInboxId( - peerInboxId: InboxId + peerInboxId: InboxId, + disappearingMessageSettings?: DisappearingMessageSettings | undefined ): Promise> { - return await XMTPModule.findOrCreateDmWithInboxId(this.client, peerInboxId) + return await XMTPModule.findOrCreateDmWithInboxId( + this.client, + peerInboxId, + disappearingMessageSettings?.disappearStartingAtNs, + disappearingMessageSettings?.retentionDurationInNs + ) } /** @@ -179,7 +205,9 @@ export default class Conversations< opts?.permissionLevel, opts?.name, opts?.imageUrlSquare, - opts?.description + opts?.description, + opts?.disappearingMessageSettings?.disappearStartingAtNs, + opts?.disappearingMessageSettings?.retentionDurationInNs ) } @@ -204,7 +232,9 @@ export default class Conversations< permissionPolicySet, opts?.name, opts?.imageUrlSquare, - opts?.description + opts?.description, + opts?.disappearingMessageSettings?.disappearStartingAtNs, + opts?.disappearingMessageSettings?.retentionDurationInNs ) } @@ -227,7 +257,9 @@ export default class Conversations< opts?.permissionLevel, opts?.name, opts?.imageUrlSquare, - opts?.description + opts?.description, + opts?.disappearingMessageSettings?.disappearStartingAtNs, + opts?.disappearingMessageSettings?.retentionDurationInNs ) } @@ -252,7 +284,9 @@ export default class Conversations< permissionPolicySet, opts?.name, opts?.imageUrlSquare, - opts?.description + opts?.description, + opts?.disappearingMessageSettings?.disappearStartingAtNs, + opts?.disappearingMessageSettings?.retentionDurationInNs ) } diff --git a/src/lib/types/CreateGroupOptions.ts b/src/lib/types/CreateGroupOptions.ts index c527f026..0548b6a5 100644 --- a/src/lib/types/CreateGroupOptions.ts +++ b/src/lib/types/CreateGroupOptions.ts @@ -3,4 +3,10 @@ export type CreateGroupOptions = { name?: string | undefined imageUrlSquare?: string | undefined description?: string | undefined + disappearingMessageSettings?: DisappearingMessageSettings | undefined +} + +export type DisappearingMessageSettings = { + disappearStartingAtNs: number + retentionDurationInNs: number } From 8800d7a26307fb7855dea5d6da879475d26e7f37 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 16 Feb 2025 15:58:43 -0800 Subject: [PATCH 13/20] add the index methods --- src/index.ts | 46 ++++++++++++++++++++++++++ src/lib/Conversations.ts | 6 ++-- src/lib/DisappearingMessageSettings.ts | 17 ++++++++++ src/lib/types/CreateGroupOptions.ts | 7 ++-- 4 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 src/lib/DisappearingMessageSettings.ts diff --git a/src/index.ts b/src/index.ts index 5e2ae833..763ee960 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,6 +33,7 @@ import { DefaultContentTypes } from './lib/types/DefaultContentType' import { MessageId, MessageOrder } from './lib/types/MessagesOptions' import { PermissionPolicySet } from './lib/types/PermissionPolicySet' import { getAddress } from './utils/address' +import { DisappearingMessageSettings } from './lib/types' export * from './context' export * from './hooks' @@ -966,6 +967,51 @@ export function updateGroupDescription( return XMTPModule.updateGroupDescription(installationId, id, description) } +export async function disappearingMessageSettings( + installationId: string, + conversationId: string +): Promise { + const settings = JSON.parse( + await XMTPModule.disappearingMessageSettings(installationId, conversationId) + ) + + return new DisappearingMessageSettings(settings) +} + +export async function isDisappearingMessagesEnabled( + installationId: string, + conversationId: string +): Promise { + return await XMTPModule.isDisappearingMessagesEnabled( + installationId, + conversationId + ) +} + +export async function clearDisappearingMessageSettings( + installationId: string, + conversationId: string +): Promise { + return await XMTPModule.clearDisappearingMessageSettings( + installationId, + conversationId + ) +} + +export async function updateDisappearingMessageSettings( + installationId: string, + conversationId: string, + startAtNs: number, + durationInNs: number +): Promise { + return await XMTPModule.updateDisappearingMessageSettings( + installationId, + conversationId, + startAtNs, + durationInNs + ) +} + export function isGroupActive( installationId: InstallationId, id: ConversationId diff --git a/src/lib/Conversations.ts b/src/lib/Conversations.ts index 335df35d..469ac260 100644 --- a/src/lib/Conversations.ts +++ b/src/lib/Conversations.ts @@ -3,13 +3,11 @@ import { keystore } from '@xmtp/proto' import { Client, InboxId } from './Client' import { ConversationVersion } from './Conversation' import { DecodedMessage } from './DecodedMessage' +import { DisappearingMessageSettings } from './DisappearingMessageSettings' import { Dm, DmParams } from './Dm' import { Group, GroupParams } from './Group' import { ConversationOptions } from './types/ConversationOptions' -import { - CreateGroupOptions, - DisappearingMessageSettings, -} from './types/CreateGroupOptions' +import { CreateGroupOptions } from './types/CreateGroupOptions' import { DecodedMessageUnion } from './types/DecodedMessageUnion' import { DefaultContentTypes } from './types/DefaultContentType' import { EventTypes } from './types/EventTypes' diff --git a/src/lib/DisappearingMessageSettings.ts b/src/lib/DisappearingMessageSettings.ts new file mode 100644 index 00000000..2ad2931e --- /dev/null +++ b/src/lib/DisappearingMessageSettings.ts @@ -0,0 +1,17 @@ +export class DisappearingMessageSettings { + disappearStartingAtNs: number + retentionDurationInNs: number + + constructor(disappearStartingAtNs: number, retentionDurationInNs: number) { + this.disappearStartingAtNs = disappearStartingAtNs + this.retentionDurationInNs = retentionDurationInNs + } + + static from(json: string): DisappearingMessageSettings { + const entry = JSON.parse(json) + return new DisappearingMessageSettings( + entry.disappearStartingAtNs, + entry.retentionDurationInNs + ) + } +} diff --git a/src/lib/types/CreateGroupOptions.ts b/src/lib/types/CreateGroupOptions.ts index 0548b6a5..853398e5 100644 --- a/src/lib/types/CreateGroupOptions.ts +++ b/src/lib/types/CreateGroupOptions.ts @@ -1,3 +1,5 @@ +import { DisappearingMessageSettings } from '../DisappearingMessageSettings' + export type CreateGroupOptions = { permissionLevel?: 'all_members' | 'admin_only' | undefined name?: string | undefined @@ -5,8 +7,3 @@ export type CreateGroupOptions = { description?: string | undefined disappearingMessageSettings?: DisappearingMessageSettings | undefined } - -export type DisappearingMessageSettings = { - disappearStartingAtNs: number - retentionDurationInNs: number -} From 7a6a1285a0b28ed66d20e4c7d81fd0cd217a2ca7 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 16 Feb 2025 16:07:14 -0800 Subject: [PATCH 14/20] add the interface and conversation methods --- src/index.ts | 8 ++++-- src/lib/Conversation.ts | 17 +++++++++++- src/lib/Dm.ts | 57 +++++++++++++++++++++++++++++++++++++++- src/lib/Group.ts | 58 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 135 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 763ee960..150a5432 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ import { } from './lib/ContentCodec' import { Conversation, ConversationVersion } from './lib/Conversation' import { DecodedMessage, MessageDeliveryStatus } from './lib/DecodedMessage' +import { DisappearingMessageSettings } from './lib/DisappearingMessageSettings' import { Dm } from './lib/Dm' import { Group, PermissionUpdateOption } from './lib/Group' import { InboxState } from './lib/InboxState' @@ -33,7 +34,6 @@ import { DefaultContentTypes } from './lib/types/DefaultContentType' import { MessageId, MessageOrder } from './lib/types/MessagesOptions' import { PermissionPolicySet } from './lib/types/PermissionPolicySet' import { getAddress } from './utils/address' -import { DisappearingMessageSettings } from './lib/types' export * from './context' export * from './hooks' @@ -975,7 +975,10 @@ export async function disappearingMessageSettings( await XMTPModule.disappearingMessageSettings(installationId, conversationId) ) - return new DisappearingMessageSettings(settings) + return new DisappearingMessageSettings( + settings.disappearStartingAtNs, + settings.retentionDurationInNs + ) } export async function isDisappearingMessagesEnabled( @@ -1393,3 +1396,4 @@ export { } from './lib/types/ConversationOptions' export { MessageId, MessageOrder } from './lib/types/MessagesOptions' export { DecodedMessageUnion } from './lib/types/DecodedMessageUnion' +export { DisappearingMessageSettings } from './lib/DisappearingMessageSettings' diff --git a/src/lib/Conversation.ts b/src/lib/Conversation.ts index 9457b01e..7529c4a2 100644 --- a/src/lib/Conversation.ts +++ b/src/lib/Conversation.ts @@ -7,7 +7,14 @@ import { } from './types' import { DecodedMessageUnion } from './types/DecodedMessageUnion' import { DefaultContentTypes } from './types/DefaultContentType' -import { DecodedMessage, Member, Dm, Group, Client } from '../index' +import { + DecodedMessage, + Member, + Dm, + Group, + Client, + DisappearingMessageSettings, +} from '../index' export enum ConversationVersion { GROUP = 'GROUP', @@ -38,6 +45,14 @@ export interface ConversationBase { ): Promise<() => void> consentState(): Promise updateConsent(state: ConsentState): Promise + disappearingMessageSettings(): Promise< + DisappearingMessageSettings | undefined + > + isDisappearingMessagesEnabled(): Promise + clearDisappearingMessageSettings(): Promise + updateDisappearingMessageSettings( + disappearingMessageSettings: DisappearingMessageSettings + ): Promise processMessage( encryptedMessage: string ): Promise> diff --git a/src/lib/Dm.ts b/src/lib/Dm.ts index 85d0f146..85621f2c 100644 --- a/src/lib/Dm.ts +++ b/src/lib/Dm.ts @@ -10,7 +10,11 @@ import { EventTypes } from './types/EventTypes' import { MessageId, MessagesOptions } from './types/MessagesOptions' import { SendOptions } from './types/SendOptions' import * as XMTP from '../index' -import { ConversationId, ConversationTopic } from '../index' +import { + ConversationId, + ConversationTopic, + DisappearingMessageSettings, +} from '../index' export interface DmParams { id: ConversationId @@ -301,6 +305,57 @@ export class Dm ) } + /** + * Returns the disappearing message settings. + * To get the latest settings from the network, call sync() first. + * @returns {Promise} A Promise that resolves to the disappearing message settings. + */ + async disappearingMessageSettings(): Promise< + DisappearingMessageSettings | undefined + > { + return XMTP.disappearingMessageSettings(this.client.installationId, this.id) + } + + /** + * Checks if disappearing messages are enabled. + * @returns {Promise} A Promise that resolves to a boolean indicating whether disappearing messages are enabled. + */ + async isDisappearingMessagesEnabled(): Promise { + return XMTP.isDisappearingMessagesEnabled( + this.client.installationId, + this.id + ) + } + + /** + * Clears the disappearing message settings for this group. + * Will throw if the user does not have the required permissions. + * @returns {Promise} A Promise that resolves when the settings are cleared. + */ + async clearDisappearingMessageSettings(): Promise { + return XMTP.clearDisappearingMessageSettings( + this.client.installationId, + this.id + ) + } + + /** + * Updates the disappearing message settings. + * Will throw if the user does not have the required permissions. + * @param {DisappearingMessageSettings} disappearingMessageSettings The new disappearing message setting. + * @returns {Promise} A Promise that resolves when the settings are updated. + */ + async updateDisappearingMessageSettings( + disappearingMessageSettings: DisappearingMessageSettings + ): Promise { + return XMTP.updateDisappearingMessageSettings( + this.client.installationId, + this.id, + disappearingMessageSettings.disappearStartingAtNs, + disappearingMessageSettings.retentionDurationInNs + ) + } + /** * * @returns {Promise} A Promise that resolves to an array of Member objects. diff --git a/src/lib/Group.ts b/src/lib/Group.ts index 09b5f36c..0b4b853a 100644 --- a/src/lib/Group.ts +++ b/src/lib/Group.ts @@ -11,7 +11,12 @@ import { MessageId, MessagesOptions } from './types/MessagesOptions' import { PermissionPolicySet } from './types/PermissionPolicySet' import { SendOptions } from './types/SendOptions' import * as XMTP from '../index' -import { Address, ConversationId, ConversationTopic } from '../index' +import { + Address, + ConversationId, + ConversationTopic, + DisappearingMessageSettings, +} from '../index' export type PermissionUpdateOption = 'allow' | 'deny' | 'admin' | 'super_admin' @@ -416,6 +421,57 @@ export class Group< ) } + /** + * Returns the disappearing message settings. + * To get the latest settings from the network, call sync() first. + * @returns {Promise} A Promise that resolves to the disappearing message settings. + */ + async disappearingMessageSettings(): Promise< + DisappearingMessageSettings | undefined + > { + return XMTP.disappearingMessageSettings(this.client.installationId, this.id) + } + + /** + * Checks if disappearing messages are enabled. + * @returns {Promise} A Promise that resolves to a boolean indicating whether disappearing messages are enabled. + */ + async isDisappearingMessagesEnabled(): Promise { + return XMTP.isDisappearingMessagesEnabled( + this.client.installationId, + this.id + ) + } + + /** + * Clears the disappearing message settings for this group. + * Will throw if the user does not have the required permissions. + * @returns {Promise} A Promise that resolves when the settings are cleared. + */ + async clearDisappearingMessageSettings(): Promise { + return XMTP.clearDisappearingMessageSettings( + this.client.installationId, + this.id + ) + } + + /** + * Updates the disappearing message settings. + * Will throw if the user does not have the required permissions. + * @param {DisappearingMessageSettings} disappearingMessageSettings The new disappearing message setting. + * @returns {Promise} A Promise that resolves when the settings are updated. + */ + async updateDisappearingMessageSettings( + disappearingMessageSettings: DisappearingMessageSettings + ): Promise { + return XMTP.updateDisappearingMessageSettings( + this.client.installationId, + this.id, + disappearingMessageSettings.disappearStartingAtNs, + disappearingMessageSettings.retentionDurationInNs + ) + } + /** * Returns whether the group is active. * To get the latest active status from the network, call sync() first From 0657b9f5a5204ee94e6ae46d1573f05ece9e5796 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 16 Feb 2025 21:02:29 -0800 Subject: [PATCH 15/20] first pass on a test --- example/src/tests/groupTests.ts | 3620 ++++++++++++++++--------------- 1 file changed, 1906 insertions(+), 1714 deletions(-) diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index c5e2b671..44e37357 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -18,6 +18,7 @@ import { DecodedMessage, ConsentRecord, } from '../../../src/index' +import { PermissionPolicySet } from 'xmtp-react-native-sdk/lib/types/PermissionPolicySet' export const groupTests: Test[] = [] let counter = 1 @@ -25,1990 +26,2181 @@ function test(name: string, perform: () => Promise) { groupTests.push({ name: String(counter++) + '. ' + name, run: perform }) } -test('verify exportNativeLogs', async () => { - await createClients(2) - const logs = await Client.exportNativeLogs() - assert( - logs.includes('Created XMTP client for inbox_id'), - 'Logs should contain Initialized identity inbox_id=' - ) - return true -}) - -test('can create a group with inbox ids default permissions', async () => { - const [alix, bo, caro] = await createClients(3) - - // Create group with inbox ID - const boGroup = await bo.conversations.newGroupWithInboxIds([alix.inboxId]) - - await alix.conversations.sync() - await boGroup.sync() - - const alixGroups = await alix.conversations.listGroups() - const alixGroup = alixGroups[0] - - // Verify group IDs are not empty - assert(boGroup.id !== '', 'bo group ID should not be empty') - assert(alixGroup.id !== '', 'alix group ID should not be empty') - - // Add caro to group - await alixGroup.addMembers([caro.address]) - await boGroup.sync() - - // Verify member counts - assert( - (await alixGroup.members()).length === 3, - 'alix group should have 3 members' - ) - assert( - (await boGroup.members()).length === 3, - 'bo group should have 3 members' - ) - - // Verify remove members throws error (admin only) - try { - await alixGroup.removeMembers([caro.address]) - await boGroup.sync() - throw new Error('Should not be able to remove members') - } catch { - // Expected error - } - - // Verify member counts unchanged - assert( - (await alixGroup.members()).length === 3, - 'alix group should still have 3 members' - ) - assert( - (await boGroup.members()).length === 3, - 'bo group should still have 3 members' - ) - - // Check permissions - const boPermissions = await boGroup.permissionPolicySet() - const alixPermissions = await alixGroup.permissionPolicySet() - - assert( - boPermissions.addMemberPolicy === 'allow', - 'bo group should have allow add member policy' - ) - assert( - alixPermissions.addMemberPolicy === 'allow', - 'alix group should have allow add member policy' - ) - - // Check super admin status - assert( - await boGroup.isSuperAdmin(bo.inboxId), - 'bo should be super admin in bo group' - ) - assert( - !(await boGroup.isSuperAdmin(alix.inboxId)), - 'alix should not be super admin in bo group' - ) - assert( - await alixGroup.isSuperAdmin(bo.inboxId), - 'bo should be super admin in alix group' - ) - assert( - !(await alixGroup.isSuperAdmin(alix.inboxId)), - 'alix should not be super admin in alix group' - ) - - return true -}) - -test('groups cannot fork', async () => { - const [alix, bo, caro] = await createClients(3) - // Create group with 3 users - const { id: groupId } = await alix.conversations.newGroup([ - bo.address, - caro.address, - ]) - - const getGroupForClient = async (client: Client) => { - // Always sync the client before getting the group - await client.conversations.sync() - const group = await client.conversations.findGroup(groupId) - assert(group !== undefined, `Group not found for ${client.address}`) - return group as Group - } - - const syncClientAndGroup = async (client: Client) => { - const group = await getGroupForClient(client) - await group.sync() - } - - const addMemberToGroup = async (fromClient: Client, addresses: string[]) => { - await syncClientAndGroup(fromClient) - const group = await getGroupForClient(fromClient) - await group.addMembers(addresses) - await delayToPropogate(500) - } - - const removeMemberFromGroup = async ( - fromClient: Client, - addresses: string[] - ) => { - await syncClientAndGroup(fromClient) - const group = await getGroupForClient(fromClient) - await group.removeMembers(addresses) - await delayToPropogate(500) - } - - // Helper to send a message from a bunch of senders and make sure it is received by all receivers - const testMessageSending = async (senderClient: Client, receiver: Client) => { - // for (const senderClient of senders) { - const messageContent = Math.random().toString(36) - await syncClientAndGroup(senderClient) - const senderGroup = await getGroupForClient(senderClient) - await senderGroup.send(messageContent) - - await delayToPropogate(500) - await senderGroup.sync() - - await syncClientAndGroup(receiver) - - const receiverGroupToCheck = await getGroupForClient(receiver) - await receiverGroupToCheck.sync() - - const messages = await receiverGroupToCheck.messages({ - direction: 'DESCENDING', - }) - const lastMessage = messages[0] - // console.log(lastMessage); - console.log( - `${receiverGroupToCheck.client.installationId} sees ${messages.length} messages in group` - ) - assert( - lastMessage !== undefined && - lastMessage.nativeContent.text === messageContent, - `${receiverGroupToCheck.client.installationId} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` - ) - // } - } - - console.log('Testing that messages sent by alix are received by bo') - await testMessageSending(alix, bo) - console.log('Alix & Bo are not forked at the beginning') - - // Test adding members one by one - // console.log('Testing adding members one by one...') - const newClients = await createClients(2) - - // Add back several members - console.log('Adding new members to the group...') - for (const client of newClients) { - console.log(`Adding member ${client.address}...`) - await addMemberToGroup(alix, [client.address]) - } - await delayToPropogate() - - await alix.conversations.sync() - await syncClientAndGroup(alix) - - // NB => if we don't use Promise.all but a loop, we don't get a fork - const REMOVE_MEMBERS_IN_PARALLEL = true - if (REMOVE_MEMBERS_IN_PARALLEL) { - console.log('Removing members in parallel') - - await Promise.all( - newClients.map((client) => { - console.log(`Removing member ${client.address}...`) - return removeMemberFromGroup(alix, [client.address]) - }) - ) - } else { - console.log('Removing members one by one') - - for (const client of newClients) { - console.log(`Removing member ${client.address}...`) - await removeMemberFromGroup(alix, [client.address]) - } - } - - await delayToPropogate(1000) - - // When forked, it stays forked even if we try 5 times - // but sometimes it is not forked and works 5/5 times - let forkCount = 0 - const tryCount = 5 - for (let i = 0; i < tryCount; i++) { - console.log(`Checking fork status ${i + 1}/${tryCount}`) - try { - await syncClientAndGroup(alix) - await syncClientAndGroup(bo) - await delayToPropogate(500) - await testMessageSending(alix, bo) - console.log('Not forked!') - } catch (e: any) { - console.log('Forked!') - console.log(e) - forkCount++ - } - } - - assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) - - return true -}) - -test('groups cannot fork short version', async () => { - const [alix, bo, new_one, new_two] = await createClients(4) - // Create group with 2 users - const alixGroup = await alix.conversations.newGroup([ - bo.address, - new_one.address, - new_two.address, - ]) - - // sync clients - await alix.conversations.sync() - await bo.conversations.sync() - const boGroup: Group = (await bo.conversations.findGroup( - alixGroup.id - ))! - - // Remove two members in parallel - // NB => if we don't use Promise.all but a loop, we don't get a fork - console.log( - '*************libxmtp*********************: Removing members in parallel' - ) - await Promise.all([ - alixGroup.removeMembers([new_one.address]), - alixGroup.removeMembers([new_two.address]), - ]) - - // Helper to send a message from a bunch of senders and make sure it is received by all receivers - const testMessageSending = async ( - senderGroup: Group, - receiverGroup: Group - ) => { - const messageContent = Math.random().toString(36) - await senderGroup.sync() - await alixGroup.send(messageContent) - - await delayToPropogate(500) - await alixGroup.sync() - await receiverGroup.sync() - - const messages = await receiverGroup.messages({ - direction: 'DESCENDING', - }) - const lastMessage = messages[0] - console.log( - `${receiverGroup.client.installationId} sees ${messages.length} messages in group` - ) - assert( - lastMessage !== undefined && - lastMessage.nativeContent.text === messageContent, - `${receiverGroup.client.installationId} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` - ) - } - // When forked, it stays forked even if we try 5 times - // but sometimes it is not forked and works 5/5 times - let forkCount = 0 - const tryCount = 5 - for (let i = 0; i < tryCount; i++) { - console.log(`Checking fork status ${i + 1}/${tryCount}`) - try { - await alixGroup.sync() - await boGroup.sync() - await delayToPropogate(500) - await testMessageSending(alixGroup, boGroup) - console.log('Not forked!') - } catch (e: any) { - console.log('Forked!') - console.log(e) - forkCount++ - } - } - assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) - - return true -}) - -test('groups cannot fork short version - update metadata', async () => { - const [alix, bo, new_one, new_two] = await createClients(6) - // Create group with 2 users - const alixGroup = await alix.conversations.newGroup([ - bo.address, - new_one.address, - new_two.address, - ]) - - // sync clients - await alix.conversations.sync() - await bo.conversations.sync() - const boGroup: Group = (await bo.conversations.findGroup( - alixGroup.id - ))! - - // Remove two members in parallel - // NB => if we don't use Promise.all but a loop, we don't get a fork - console.log( - '*************libxmtp*********************: Updating metadata in parallel' - ) - await Promise.all([ - alixGroup.updateGroupName('new name'), - alixGroup.updateGroupName('new name 2'), - ]) - - // Helper to send a message from a bunch of senders and make sure it is received by all receivers - const testMessageSending = async ( - senderGroup: Group, - receiverGroup: Group - ) => { - const messageContent = Math.random().toString(36) - await senderGroup.sync() - await alixGroup.send(messageContent) - - await delayToPropogate(500) - await alixGroup.sync() - await receiverGroup.sync() - - const messages = await receiverGroup.messages({ - direction: 'DESCENDING', - }) - const lastMessage = messages[0] - console.log( - `${receiverGroup.client.installationId} sees ${messages.length} messages in group` - ) - assert( - lastMessage !== undefined && - lastMessage.nativeContent.text === messageContent, - `${receiverGroup.client.installationId} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` - ) - } - // When forked, it stays forked even if we try 5 times - // but sometimes it is not forked and works 5/5 times - let forkCount = 0 - const tryCount = 5 - for (let i = 0; i < tryCount; i++) { - console.log(`Checking fork status ${i + 1}/${tryCount}`) - try { - await alixGroup.sync() - await boGroup.sync() - await delayToPropogate(500) - await testMessageSending(alixGroup, boGroup) - console.log('Not forked!') - } catch (e: any) { - console.log('Forked!') - console.log(e) - forkCount++ - } - } - assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) - - return true -}) - -test('can cancel streams', async () => { - const [alix, bo] = await createClients(2) - let messageCallbacks = 0 - - await bo.conversations.streamAllMessages(async () => { - messageCallbacks++ - }) - - const group = await alix.conversations.newGroup([bo.address]) - await group.send('hello') - await delayToPropogate() - - assert( - messageCallbacks === 1, - 'message stream should have received 1 message' - ) - - await bo.conversations.cancelStreamAllMessages() - await delayToPropogate() - - await group.send('hello') - await group.send('hello') - await group.send('hello') +// test('verify exportNativeLogs', async () => { +// await createClients(2) +// const logs = await Client.exportNativeLogs() +// assert( +// logs.includes('Created XMTP client for inbox_id'), +// 'Logs should contain Initialized identity inbox_id=' +// ) +// return true +// }) - await delayToPropogate() +// test('can create a group with inbox ids default permissions', async () => { +// const [alix, bo, caro] = await createClients(3) + +// // Create group with inbox ID +// const boGroup = await bo.conversations.newGroupWithInboxIds([alix.inboxId]) + +// await alix.conversations.sync() +// await boGroup.sync() + +// const alixGroups = await alix.conversations.listGroups() +// const alixGroup = alixGroups[0] + +// // Verify group IDs are not empty +// assert(boGroup.id !== '', 'bo group ID should not be empty') +// assert(alixGroup.id !== '', 'alix group ID should not be empty') + +// // Add caro to group +// await alixGroup.addMembers([caro.address]) +// await boGroup.sync() + +// // Verify member counts +// assert( +// (await alixGroup.members()).length === 3, +// 'alix group should have 3 members' +// ) +// assert( +// (await boGroup.members()).length === 3, +// 'bo group should have 3 members' +// ) + +// // Verify remove members throws error (admin only) +// try { +// await alixGroup.removeMembers([caro.address]) +// await boGroup.sync() +// throw new Error('Should not be able to remove members') +// } catch { +// // Expected error +// } - assert( - messageCallbacks === 1, - 'message stream should still only received 1 message' - ) +// // Verify member counts unchanged +// assert( +// (await alixGroup.members()).length === 3, +// 'alix group should still have 3 members' +// ) +// assert( +// (await boGroup.members()).length === 3, +// 'bo group should still have 3 members' +// ) + +// // Check permissions +// const boPermissions = await boGroup.permissionPolicySet() +// const alixPermissions = await alixGroup.permissionPolicySet() + +// assert( +// boPermissions.addMemberPolicy === 'allow', +// 'bo group should have allow add member policy' +// ) +// assert( +// alixPermissions.addMemberPolicy === 'allow', +// 'alix group should have allow add member policy' +// ) + +// // Check super admin status +// assert( +// await boGroup.isSuperAdmin(bo.inboxId), +// 'bo should be super admin in bo group' +// ) +// assert( +// !(await boGroup.isSuperAdmin(alix.inboxId)), +// 'alix should not be super admin in bo group' +// ) +// assert( +// await alixGroup.isSuperAdmin(bo.inboxId), +// 'bo should be super admin in alix group' +// ) +// assert( +// !(await alixGroup.isSuperAdmin(alix.inboxId)), +// 'alix should not be super admin in alix group' +// ) - await bo.conversations.streamAllMessages(async () => { - messageCallbacks++ - }) +// return true +// }) - await delayToPropogate() +// test('groups cannot fork', async () => { +// const [alix, bo, caro] = await createClients(3) +// // Create group with 3 users +// const { id: groupId } = await alix.conversations.newGroup([ +// bo.address, +// caro.address, +// ]) + +// const getGroupForClient = async (client: Client) => { +// // Always sync the client before getting the group +// await client.conversations.sync() +// const group = await client.conversations.findGroup(groupId) +// assert(group !== undefined, `Group not found for ${client.address}`) +// return group as Group +// } - await group.send('hello') - await delayToPropogate() +// const syncClientAndGroup = async (client: Client) => { +// const group = await getGroupForClient(client) +// await group.sync() +// } - assert( - messageCallbacks === 2, - 'message stream should have received 2 message' - ) +// const addMemberToGroup = async (fromClient: Client, addresses: string[]) => { +// await syncClientAndGroup(fromClient) +// const group = await getGroupForClient(fromClient) +// await group.addMembers(addresses) +// await delayToPropogate(500) +// } - return true -}) +// const removeMemberFromGroup = async ( +// fromClient: Client, +// addresses: string[] +// ) => { +// await syncClientAndGroup(fromClient) +// const group = await getGroupForClient(fromClient) +// await group.removeMembers(addresses) +// await delayToPropogate(500) +// } -test('group message delivery status', async () => { - const [alixClient, boClient] = await createClients(2) - const alixGroup = await alixClient.conversations.newGroup([boClient.address]) +// // Helper to send a message from a bunch of senders and make sure it is received by all receivers +// const testMessageSending = async (senderClient: Client, receiver: Client) => { +// // for (const senderClient of senders) { +// const messageContent = Math.random().toString(36) +// await syncClientAndGroup(senderClient) +// const senderGroup = await getGroupForClient(senderClient) +// await senderGroup.send(messageContent) + +// await delayToPropogate(500) +// await senderGroup.sync() + +// await syncClientAndGroup(receiver) + +// const receiverGroupToCheck = await getGroupForClient(receiver) +// await receiverGroupToCheck.sync() + +// const messages = await receiverGroupToCheck.messages({ +// direction: 'DESCENDING', +// }) +// const lastMessage = messages[0] +// // console.log(lastMessage); +// console.log( +// `${receiverGroupToCheck.client.installationId} sees ${messages.length} messages in group` +// ) +// assert( +// lastMessage !== undefined && +// lastMessage.nativeContent.text === messageContent, +// `${receiverGroupToCheck.client.installationId} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` +// ) +// // } +// } - await alixGroup.send('hello, world') +// console.log('Testing that messages sent by alix are received by bo') +// await testMessageSending(alix, bo) +// console.log('Alix & Bo are not forked at the beginning') - const alixMessages: DecodedMessage[] = await alixGroup.messages() +// // Test adding members one by one +// // console.log('Testing adding members one by one...') +// const newClients = await createClients(2) - assert( - alixMessages.length === 2, - `the messages length should be 2 but was ${alixMessages.length}` - ) +// // Add back several members +// console.log('Adding new members to the group...') +// for (const client of newClients) { +// console.log(`Adding member ${client.address}...`) +// await addMemberToGroup(alix, [client.address]) +// } +// await delayToPropogate() - await alixGroup.sync() - const alixMessages2: DecodedMessage[] = await alixGroup.messages() +// await alix.conversations.sync() +// await syncClientAndGroup(alix) + +// // NB => if we don't use Promise.all but a loop, we don't get a fork +// const REMOVE_MEMBERS_IN_PARALLEL = true +// if (REMOVE_MEMBERS_IN_PARALLEL) { +// console.log('Removing members in parallel') + +// await Promise.all( +// newClients.map((client) => { +// console.log(`Removing member ${client.address}...`) +// return removeMemberFromGroup(alix, [client.address]) +// }) +// ) +// } else { +// console.log('Removing members one by one') + +// for (const client of newClients) { +// console.log(`Removing member ${client.address}...`) +// await removeMemberFromGroup(alix, [client.address]) +// } +// } - assert( - alixMessages2.length === 2, - `the messages length should be 2 but was ${alixMessages.length}` - ) +// await delayToPropogate(1000) + +// // When forked, it stays forked even if we try 5 times +// // but sometimes it is not forked and works 5/5 times +// let forkCount = 0 +// const tryCount = 5 +// for (let i = 0; i < tryCount; i++) { +// console.log(`Checking fork status ${i + 1}/${tryCount}`) +// try { +// await syncClientAndGroup(alix) +// await syncClientAndGroup(bo) +// await delayToPropogate(500) +// await testMessageSending(alix, bo) +// console.log('Not forked!') +// } catch (e: any) { +// console.log('Forked!') +// console.log(e) +// forkCount++ +// } +// } - assert( - alixMessages2[0].deliveryStatus === 'PUBLISHED', - `the message should have a delivery status of PUBLISHED but was ${alixMessages2[0].deliveryStatus}` - ) +// assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) - await boClient.conversations.sync() - const boGroup = (await boClient.conversations.listGroups())[0] - await boGroup.sync() - const boMessages: DecodedMessage[] = await boGroup.messages() +// return true +// }) - assert( - boMessages.length === 1, - `the messages length should be 1 but was ${boMessages.length}` - ) +// test('groups cannot fork short version', async () => { +// const [alix, bo, new_one, new_two] = await createClients(4) +// // Create group with 2 users +// const alixGroup = await alix.conversations.newGroup([ +// bo.address, +// new_one.address, +// new_two.address, +// ]) + +// // sync clients +// await alix.conversations.sync() +// await bo.conversations.sync() +// const boGroup: Group = (await bo.conversations.findGroup( +// alixGroup.id +// ))! + +// // Remove two members in parallel +// // NB => if we don't use Promise.all but a loop, we don't get a fork +// console.log( +// '*************libxmtp*********************: Removing members in parallel' +// ) +// await Promise.all([ +// alixGroup.removeMembers([new_one.address]), +// alixGroup.removeMembers([new_two.address]), +// ]) + +// // Helper to send a message from a bunch of senders and make sure it is received by all receivers +// const testMessageSending = async ( +// senderGroup: Group, +// receiverGroup: Group +// ) => { +// const messageContent = Math.random().toString(36) +// await senderGroup.sync() +// await alixGroup.send(messageContent) + +// await delayToPropogate(500) +// await alixGroup.sync() +// await receiverGroup.sync() + +// const messages = await receiverGroup.messages({ +// direction: 'DESCENDING', +// }) +// const lastMessage = messages[0] +// console.log( +// `${receiverGroup.client.installationId} sees ${messages.length} messages in group` +// ) +// assert( +// lastMessage !== undefined && +// lastMessage.nativeContent.text === messageContent, +// `${receiverGroup.client.installationId} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` +// ) +// } +// // When forked, it stays forked even if we try 5 times +// // but sometimes it is not forked and works 5/5 times +// let forkCount = 0 +// const tryCount = 5 +// for (let i = 0; i < tryCount; i++) { +// console.log(`Checking fork status ${i + 1}/${tryCount}`) +// try { +// await alixGroup.sync() +// await boGroup.sync() +// await delayToPropogate(500) +// await testMessageSending(alixGroup, boGroup) +// console.log('Not forked!') +// } catch (e: any) { +// console.log('Forked!') +// console.log(e) +// forkCount++ +// } +// } +// assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) - assert( - boMessages[0].deliveryStatus === 'PUBLISHED', - `the message should have a delivery status of PUBLISHED but was ${boMessages[0].deliveryStatus}` - ) +// return true +// }) - return true -}) +// test('groups cannot fork short version - update metadata', async () => { +// const [alix, bo, new_one, new_two] = await createClients(6) +// // Create group with 2 users +// const alixGroup = await alix.conversations.newGroup([ +// bo.address, +// new_one.address, +// new_two.address, +// ]) + +// // sync clients +// await alix.conversations.sync() +// await bo.conversations.sync() +// const boGroup: Group = (await bo.conversations.findGroup( +// alixGroup.id +// ))! + +// // Remove two members in parallel +// // NB => if we don't use Promise.all but a loop, we don't get a fork +// console.log( +// '*************libxmtp*********************: Updating metadata in parallel' +// ) +// await Promise.all([ +// alixGroup.updateGroupName('new name'), +// alixGroup.updateGroupName('new name 2'), +// ]) + +// // Helper to send a message from a bunch of senders and make sure it is received by all receivers +// const testMessageSending = async ( +// senderGroup: Group, +// receiverGroup: Group +// ) => { +// const messageContent = Math.random().toString(36) +// await senderGroup.sync() +// await alixGroup.send(messageContent) + +// await delayToPropogate(500) +// await alixGroup.sync() +// await receiverGroup.sync() + +// const messages = await receiverGroup.messages({ +// direction: 'DESCENDING', +// }) +// const lastMessage = messages[0] +// console.log( +// `${receiverGroup.client.installationId} sees ${messages.length} messages in group` +// ) +// assert( +// lastMessage !== undefined && +// lastMessage.nativeContent.text === messageContent, +// `${receiverGroup.client.installationId} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` +// ) +// } +// // When forked, it stays forked even if we try 5 times +// // but sometimes it is not forked and works 5/5 times +// let forkCount = 0 +// const tryCount = 5 +// for (let i = 0; i < tryCount; i++) { +// console.log(`Checking fork status ${i + 1}/${tryCount}`) +// try { +// await alixGroup.sync() +// await boGroup.sync() +// await delayToPropogate(500) +// await testMessageSending(alixGroup, boGroup) +// console.log('Not forked!') +// } catch (e: any) { +// console.log('Forked!') +// console.log(e) +// forkCount++ +// } +// } +// assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) -test('can find a group by id', async () => { - const [alixClient, boClient] = await createClients(2) - const alixGroup = await alixClient.conversations.newGroup([boClient.address]) +// return true +// }) - await boClient.conversations.sync() - const boGroup = await boClient.conversations.findGroup(alixGroup.id) +// test('can cancel streams', async () => { +// const [alix, bo] = await createClients(2) +// let messageCallbacks = 0 - assert( - boGroup?.id === alixGroup.id, - `bo ${boGroup?.id} does not match alix ${alixGroup.id}` - ) - return true -}) +// await bo.conversations.streamAllMessages(async () => { +// messageCallbacks++ +// }) -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') +// const group = await alix.conversations.newGroup([bo.address]) +// await group.send('hello') +// await delayToPropogate() - await boClient.conversations.sync() - const boGroup = await boClient.conversations.findGroup(alixGroup.id) - await boGroup?.sync() - const boMessage = await boClient.conversations.findMessage(alixMessageId) +// assert( +// messageCallbacks === 1, +// 'message stream should have received 1 message' +// ) - assert( - boMessage?.id === alixMessageId, - `bo message ${boMessage?.id} does not match ${alixMessageId}` - ) - return true -}) +// await bo.conversations.cancelStreamAllMessages() +// await delayToPropogate() -test('who added me to a group', async () => { - const [alixClient, boClient] = await createClients(2) - await alixClient.conversations.newGroup([boClient.address]) +// await group.send('hello') +// await group.send('hello') +// await group.send('hello') - await boClient.conversations.sync() - const boGroup = (await boClient.conversations.listGroups())[0] - const addedByInboxId = await boGroup.addedByInboxId +// await delayToPropogate() - assert( - addedByInboxId === alixClient.inboxId, - `addedByInboxId ${addedByInboxId} does not match ${alixClient.inboxId}` - ) - return true -}) +// assert( +// messageCallbacks === 1, +// 'message stream should still only received 1 message' +// ) -test('can get members of a group', async () => { - const [alixClient, boClient] = await createClients(2) - const group = await alixClient.conversations.newGroup([boClient.address]) - - const members = await group.members() - - assert(members.length === 2, `Should be 2 members but was ${members.length}`) - - // We can not be sure of the order that members will be returned in - for (const member of members) { - // Alix created the group so they are a super admin - if ( - member.addresses[0].toLocaleLowerCase() === - alixClient.address.toLocaleLowerCase() - ) { - assert( - member.permissionLevel === 'super_admin', - `Should be super_admin but was ${member.permissionLevel}` - ) - } - // Bo did not create the group so he defaults to permission level "member" - if ( - member.addresses[0].toLocaleLowerCase() === - boClient.address.toLocaleLowerCase() - ) { - assert( - member.permissionLevel === 'member', - `Should be member but was ${member.permissionLevel}` - ) - } - } - return true -}) +// await bo.conversations.streamAllMessages(async () => { +// messageCallbacks++ +// }) -test('can message in a group', async () => { - const [alixClient, boClient, caroClient] = await createClients(3) +// await delayToPropogate() - // alix's num groups start at 0 - let alixGroups = await alixClient.conversations.listGroups() - if (alixGroups.length !== 0) { - throw new Error('num groups should be 0') - } +// await group.send('hello') +// await delayToPropogate() - // alix creates a group - const alixGroup = await alixClient.conversations.newGroup([ - boClient.address, - caroClient.address, - ]) - - // alix's num groups == 1 - await alixClient.conversations.sync() - alixGroups = await alixClient.conversations.listGroups() - if (alixGroups.length !== 1) { - throw new Error('num groups should be 1') - } +// assert( +// messageCallbacks === 2, +// 'message stream should have received 2 message' +// ) - // alix group should match create time from list function - assert(alixGroups[0].createdAt === alixGroup.createdAt, 'group create time') +// return true +// }) - // alix can confirm memberInboxIds - await alixGroup.sync() - const memberInboxIds = await alixGroup.memberInboxIds() - if (memberInboxIds.length !== 3) { - throw new Error('num group members should be 3') - } +// test('group message delivery status', async () => { +// const [alixClient, boClient] = await createClients(2) +// const alixGroup = await alixClient.conversations.newGroup([boClient.address]) - if ( - !( - memberInboxIds.includes(alixClient.inboxId) && - memberInboxIds.includes(boClient.inboxId) && - memberInboxIds.includes(caroClient.inboxId) - ) - ) { - throw new Error('missing address') - } +// await alixGroup.send('hello, world') - // alix can send messages - await alixGroup.send('hello, world') - await alixGroup.send('gm') - - // bo's num groups == 1 - await boClient.conversations.sync() - const boGroups = await boClient.conversations.listGroups() - if (boGroups.length !== 1) { - throw new Error( - 'num groups for bo should be 1, but it is' + boGroups.length - ) - } - await delayToPropogate() - // bo can read messages from alix - await boGroups[0].sync() - const boMessages: DecodedMessage[] = await boGroups[0].messages() - - if (boMessages.length !== 2) { - throw new Error( - 'num messages for bo should be 2, but it is' + boMessages.length - ) - } - if (boMessages[0].content() !== 'gm') { - throw new Error("newest message should be 'gm'") - } - if (boMessages[1].content() !== 'hello, world') { - throw new Error("newest message should be 'hello, world'") - } - // bo can send a message - await boGroups[0].send('hey guys!') - - // caro's num groups == 1 - await caroClient.conversations.sync() - const caroGroups = await caroClient.conversations.listGroups() - if (caroGroups.length !== 1) { - throw new Error( - 'num groups for caro should be 1, but it is' + caroGroups.length - ) - } +// const alixMessages: DecodedMessage[] = await alixGroup.messages() - // caro can read messages from alix and bo - await caroGroups[0].sync() - const caroMessages = await caroGroups[0].messages() +// assert( +// alixMessages.length === 2, +// `the messages length should be 2 but was ${alixMessages.length}` +// ) - if (caroMessages.length !== 3) { - throw new Error(`length should be 3 but was ${caroMessages.length}`) - } - if (caroMessages[0].content() !== 'hey guys!') { - throw new Error( - `newest Message should be 'hey guys!' but was ${caroMessages[0].content()}` - ) - } - if (caroMessages[1].content() !== 'gm') { - throw new Error( - `second Message should be 'gm' but was ${caroMessages[1].content()}` - ) - } +// await alixGroup.sync() +// const alixMessages2: DecodedMessage[] = await alixGroup.messages() - return true -}) +// assert( +// alixMessages2.length === 2, +// `the messages length should be 2 but was ${alixMessages.length}` +// ) -test('unpublished messages handling', async () => { - // Initialize fixture clients - const [alixClient, boClient] = await createClients(3) +// assert( +// alixMessages2[0].deliveryStatus === 'PUBLISHED', +// `the message should have a delivery status of PUBLISHED but was ${alixMessages2[0].deliveryStatus}` +// ) - // Create a new group with Bob and Alice - const boGroup = await boClient.conversations.newGroup([alixClient.address]) - assert( - (await boGroup.consentState()) === 'allowed', - 'consent should be allowed' - ) +// await boClient.conversations.sync() +// const boGroup = (await boClient.conversations.listGroups())[0] +// await boGroup.sync() +// const boMessages: DecodedMessage[] = await boGroup.messages() - // Sync Alice's client to get the new group - await alixClient.conversations.sync() - const alixGroup = await alixClient.conversations.findGroup(boGroup.id) - if (!alixGroup) { - throw new Error(`Group not found for id: ${boGroup.id}`) - } +// assert( +// boMessages.length === 1, +// `the messages length should be 1 but was ${boMessages.length}` +// ) - // Check if the group is allowed initially - const alixGroupState = await alixClient.preferences.conversationConsentState( - boGroup.id - ) - if (alixGroupState !== 'unknown') { - throw new Error('Group should not be allowed initially') - } +// assert( +// boMessages[0].deliveryStatus === 'PUBLISHED', +// `the message should have a delivery status of PUBLISHED but was ${boMessages[0].deliveryStatus}` +// ) - // Prepare a message in the group - const preparedMessageId = await alixGroup.prepareMessage('Test text') +// return true +// }) - // 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}`) - } +// test('can find a group by id', async () => { +// const [alixClient, boClient] = await createClients(2) +// const alixGroup = await alixClient.conversations.newGroup([boClient.address]) - // Publish the prepared message - await alixGroup.publishPreparedMessages() +// await boClient.conversations.sync() +// const boGroup = await boClient.conversations.findGroup(alixGroup.id) - // Sync the group after publishing the message - await alixGroup.sync() - messageCount = (await alixGroup.messages()).length - if (messageCount !== 1) { - throw new Error(`Message count should be 1, but it is ${messageCount}`) - } +// assert( +// boGroup?.id === alixGroup.id, +// `bo ${boGroup?.id} does not match alix ${alixGroup.id}` +// ) +// return true +// }) - // Check if the group is allowed after preparing the message - const isGroupAllowed = await alixClient.preferences.conversationConsentState( - boGroup.id - ) - if (isGroupAllowed !== 'allowed') { - throw new Error('Group should be allowed after preparing a message') - } +// 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') - // 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`) - } +// await boClient.conversations.sync() +// const boGroup = await boClient.conversations.findGroup(alixGroup.id) +// await boGroup?.sync() +// const boMessage = await boClient.conversations.findMessage(alixMessageId) - return true -}) +// assert( +// boMessage?.id === alixMessageId, +// `bo message ${boMessage?.id} does not match ${alixMessageId}` +// ) +// return true +// }) -test('can add members to a group', async () => { - // Create three MLS enabled Clients - const [alixClient, boClient, caroClient] = await createClients(3) +// test('who added me to a group', async () => { +// const [alixClient, boClient] = await createClients(2) +// await alixClient.conversations.newGroup([boClient.address]) - // alix's num groups start at 0 - let alixGroups = await alixClient.conversations.listGroups() - if (alixGroups.length !== 0) { - throw new Error('num groups should be 0') - } +// await boClient.conversations.sync() +// const boGroup = (await boClient.conversations.listGroups())[0] +// const addedByInboxId = await boGroup.addedByInboxId - // bo's num groups start at 0 - let boGroups = await boClient.conversations.listGroups() - if (boGroups.length !== 0) { - throw new Error('num groups should be 0') - } +// assert( +// addedByInboxId === alixClient.inboxId, +// `addedByInboxId ${addedByInboxId} does not match ${alixClient.inboxId}` +// ) +// return true +// }) - // caro's num groups start at 0 - let caroGroups = await caroClient.conversations.listGroups() - if (caroGroups.length !== 0) { - throw new Error('num groups should be 0') - } +// test('can get members of a group', async () => { +// const [alixClient, boClient] = await createClients(2) +// const group = await alixClient.conversations.newGroup([boClient.address]) + +// const members = await group.members() + +// assert(members.length === 2, `Should be 2 members but was ${members.length}`) + +// // We can not be sure of the order that members will be returned in +// for (const member of members) { +// // Alix created the group so they are a super admin +// if ( +// member.addresses[0].toLocaleLowerCase() === +// alixClient.address.toLocaleLowerCase() +// ) { +// assert( +// member.permissionLevel === 'super_admin', +// `Should be super_admin but was ${member.permissionLevel}` +// ) +// } +// // Bo did not create the group so he defaults to permission level "member" +// if ( +// member.addresses[0].toLocaleLowerCase() === +// boClient.address.toLocaleLowerCase() +// ) { +// assert( +// member.permissionLevel === 'member', +// `Should be member but was ${member.permissionLevel}` +// ) +// } +// } +// return true +// }) - // alix creates a group - const alixGroup = await alixClient.conversations.newGroup([boClient.address]) +// test('can message in a group', async () => { +// const [alixClient, boClient, caroClient] = await createClients(3) - // alix's num groups == 1 - await alixClient.conversations.sync() - alixGroups = await alixClient.conversations.listGroups() - if (alixGroups.length !== 1) { - throw new Error('num groups should be 1') - } +// // alix's num groups start at 0 +// let alixGroups = await alixClient.conversations.listGroups() +// if (alixGroups.length !== 0) { +// throw new Error('num groups should be 0') +// } - // alix can confirm memberInboxIds - await alixGroup.sync() - const memberInboxIds = await alixGroup.memberInboxIds() - if (memberInboxIds.length !== 2) { - throw new Error('num group members should be 2') - } - if ( - !( - memberInboxIds.includes(alixClient.inboxId) && - memberInboxIds.includes(boClient.inboxId) - ) - ) { - throw new Error('missing address') - } +// // alix creates a group +// const alixGroup = await alixClient.conversations.newGroup([ +// boClient.address, +// caroClient.address, +// ]) + +// // alix's num groups == 1 +// await alixClient.conversations.sync() +// alixGroups = await alixClient.conversations.listGroups() +// if (alixGroups.length !== 1) { +// throw new Error('num groups should be 1') +// } - // alix can send messages - await alixGroup.send('hello, world') - await alixGroup.send('gm') - - // bo's num groups == 1 - await boClient.conversations.sync() - boGroups = await boClient.conversations.listGroups() - if (boGroups.length !== 1) { - throw new Error( - 'num groups for bo should be 1, but it is' + boGroups.length - ) - } +// // alix group should match create time from list function +// assert(alixGroups[0].createdAt === alixGroup.createdAt, 'group create time') - await alixGroup.addMembers([caroClient.address]) +// // alix can confirm memberInboxIds +// await alixGroup.sync() +// const memberInboxIds = await alixGroup.memberInboxIds() +// if (memberInboxIds.length !== 3) { +// throw new Error('num group members should be 3') +// } - // caro's num groups == 1 - await caroClient.conversations.sync() - caroGroups = await caroClient.conversations.listGroups() - if (caroGroups.length !== 1) { - throw new Error( - 'num groups for caro should be 1, but it is' + caroGroups.length - ) - } - await caroGroups[0].sync() - const caroMessages = await caroGroups[0].messages() - if (caroMessages.length !== 0) { - throw new Error('num messages for caro should be 0') - } +// if ( +// !( +// memberInboxIds.includes(alixClient.inboxId) && +// memberInboxIds.includes(boClient.inboxId) && +// memberInboxIds.includes(caroClient.inboxId) +// ) +// ) { +// throw new Error('missing address') +// } - await boGroups[0].sync() - const boGroupMembers = await boGroups[0].memberInboxIds() - if (boGroupMembers.length !== 3) { - throw new Error('num group members should be 3') - } +// // alix can send messages +// await alixGroup.send('hello, world') +// await alixGroup.send('gm') + +// // bo's num groups == 1 +// await boClient.conversations.sync() +// const boGroups = await boClient.conversations.listGroups() +// if (boGroups.length !== 1) { +// throw new Error( +// 'num groups for bo should be 1, but it is' + boGroups.length +// ) +// } +// await delayToPropogate() +// // bo can read messages from alix +// await boGroups[0].sync() +// const boMessages: DecodedMessage[] = await boGroups[0].messages() + +// if (boMessages.length !== 2) { +// throw new Error( +// 'num messages for bo should be 2, but it is' + boMessages.length +// ) +// } +// if (boMessages[0].content() !== 'gm') { +// throw new Error("newest message should be 'gm'") +// } +// if (boMessages[1].content() !== 'hello, world') { +// throw new Error("newest message should be 'hello, world'") +// } +// // bo can send a message +// await boGroups[0].send('hey guys!') + +// // caro's num groups == 1 +// await caroClient.conversations.sync() +// const caroGroups = await caroClient.conversations.listGroups() +// if (caroGroups.length !== 1) { +// throw new Error( +// 'num groups for caro should be 1, but it is' + caroGroups.length +// ) +// } - return true -}) +// // caro can read messages from alix and bo +// await caroGroups[0].sync() +// const caroMessages = await caroGroups[0].messages() -test('can remove members from a group', async () => { - // Create three MLS enabled Clients - const [alixClient, boClient, caroClient] = await createClients(3) +// if (caroMessages.length !== 3) { +// throw new Error(`length should be 3 but was ${caroMessages.length}`) +// } +// if (caroMessages[0].content() !== 'hey guys!') { +// throw new Error( +// `newest Message should be 'hey guys!' but was ${caroMessages[0].content()}` +// ) +// } +// if (caroMessages[1].content() !== 'gm') { +// throw new Error( +// `second Message should be 'gm' but was ${caroMessages[1].content()}` +// ) +// } - // alix's num groups start at 0 - let alixGroups = await alixClient.conversations.listGroups() - if (alixGroups.length !== 0) { - throw new Error('num groups should be 0') - } +// return true +// }) - // bo's num groups start at 0 - let boGroups = await boClient.conversations.listGroups() - assert(boGroups.length === 0, 'num groups should be 0') +// 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]) +// assert( +// (await boGroup.consentState()) === 'allowed', +// 'consent should be allowed' +// ) + +// // Sync Alice's client to get the new group +// await alixClient.conversations.sync() +// const alixGroup = await alixClient.conversations.findGroup(boGroup.id) +// if (!alixGroup) { +// throw new Error(`Group not found for id: ${boGroup.id}`) +// } - // caro's num groups start at 0 - let caroGroups = await caroClient.conversations.listGroups() - if (caroGroups.length !== 0) { - throw new Error('num groups should be 0') - } +// // Check if the group is allowed initially +// const alixGroupState = await alixClient.preferences.conversationConsentState( +// boGroup.id +// ) +// if (alixGroupState !== 'unknown') { +// throw new Error('Group should not be allowed initially') +// } - // alix creates a group - const alixGroup = await alixClient.conversations.newGroup([ - boClient.address, - caroClient.address, - ]) - - // alix's num groups == 1 - await alixClient.conversations.sync() - alixGroups = await alixClient.conversations.listGroups() - if (alixGroups.length !== 1) { - throw new Error('num groups should be 1') - } +// // Prepare a message in the group +// const preparedMessageId = await alixGroup.prepareMessage('Test text') - // alix can confirm memberInboxIds - await alixGroup.sync() - const memberInboxIds = await alixGroup.memberInboxIds() - if (memberInboxIds.length !== 3) { - throw new Error('num group members should be 3') - } - if ( - !( - memberInboxIds.includes(alixClient.inboxId) && - memberInboxIds.includes(boClient.inboxId) - ) - ) { - throw new Error('missing address') - } +// // 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}`) +// } - // alix can send messages - await alixGroup.send('hello, world') - await alixGroup.send('gm') - - // bo's num groups == 1 - await boClient.conversations.sync() - boGroups = await boClient.conversations.listGroups() - if (boGroups.length !== 1) { - throw new Error( - 'num groups for bo should be 1, but it is' + boGroups.length - ) - } +// // Publish the prepared message +// await alixGroup.publishPreparedMessages() - // caro's num groups == 1 - await caroClient.conversations.sync() - caroGroups = await caroClient.conversations.listGroups() - if (caroGroups.length !== 1) { - throw new Error( - 'num groups for caro should be 1, but it is' + caroGroups.length - ) - } +// // Sync the group after publishing the message +// await alixGroup.sync() +// messageCount = (await alixGroup.messages()).length +// if (messageCount !== 1) { +// throw new Error(`Message count should be 1, but it is ${messageCount}`) +// } - await caroGroups[0].sync() - if (!caroGroups[0].isActive()) { - throw new Error('caros group should be active') - } +// // Check if the group is allowed after preparing the message +// const isGroupAllowed = await alixClient.preferences.conversationConsentState( +// boGroup.id +// ) +// if (isGroupAllowed !== 'allowed') { +// throw new Error('Group should be allowed after preparing a message') +// } - await alixGroup.removeMembers([caroClient.address]) - await alixGroup.sync() - const alixGroupMembers = await alixGroup.memberInboxIds() - if (alixGroupMembers.length !== 2) { - throw new Error( - 'num group members should be 2 but was' + alixGroupMembers.length - ) - } +// // 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`) +// } - await caroGroups[0].sync() - if (await caroGroups[0].isActive()) { - throw new Error('caros group should not be active') - } +// return true +// }) - const caroGroupMembers = await caroGroups[0].memberInboxIds() - if (caroGroupMembers.length !== 2) { - throw new Error( - 'num group members should be 2 but was' + caroGroupMembers.length - ) - } +// test('can add members to a group', async () => { +// // Create three MLS enabled Clients +// const [alixClient, boClient, caroClient] = await createClients(3) - return true -}) +// // alix's num groups start at 0 +// let alixGroups = await alixClient.conversations.listGroups() +// if (alixGroups.length !== 0) { +// throw new Error('num groups should be 0') +// } -test('can remove and add members from a group by inbox id', async () => { - // Create three MLS enabled Clients - const [alixClient, boClient, caroClient] = await createClients(3) - - // alix creates a group - const alixGroup = await alixClient.conversations.newGroup([ - boClient.address, - caroClient.address, - ]) - - // alix can confirm memberInboxIds - await alixGroup.sync() - const memberInboxIds = await alixGroup.memberInboxIds() - if (memberInboxIds.length !== 3) { - throw new Error('num group members should be 3') - } +// // bo's num groups start at 0 +// let boGroups = await boClient.conversations.listGroups() +// if (boGroups.length !== 0) { +// throw new Error('num groups should be 0') +// } - await alixGroup.removeMembersByInboxId([caroClient.inboxId]) - await alixGroup.sync() - const alixGroupMembers = await alixGroup.memberInboxIds() - if (alixGroupMembers.length !== 2) { - throw new Error('num group members should be 2') - } +// // caro's num groups start at 0 +// let caroGroups = await caroClient.conversations.listGroups() +// if (caroGroups.length !== 0) { +// throw new Error('num groups should be 0') +// } - await alixGroup.addMembersByInboxId([caroClient.inboxId]) - await alixGroup.sync() - const alixGroupMembers2 = await alixGroup.memberInboxIds() - if (alixGroupMembers2.length !== 3) { - throw new Error('num group members should be 3') - } +// // alix creates a group +// const alixGroup = await alixClient.conversations.newGroup([boClient.address]) - return true -}) +// // alix's num groups == 1 +// await alixClient.conversations.sync() +// alixGroups = await alixClient.conversations.listGroups() +// if (alixGroups.length !== 1) { +// throw new Error('num groups should be 1') +// } -test('can stream both groups and messages at same time', async () => { - const [alix, bo] = await createClients(2) +// // alix can confirm memberInboxIds +// await alixGroup.sync() +// const memberInboxIds = await alixGroup.memberInboxIds() +// if (memberInboxIds.length !== 2) { +// throw new Error('num group members should be 2') +// } +// if ( +// !( +// memberInboxIds.includes(alixClient.inboxId) && +// memberInboxIds.includes(boClient.inboxId) +// ) +// ) { +// throw new Error('missing address') +// } - let groupCallbacks = 0 - let messageCallbacks = 0 - await bo.conversations.stream(async () => { - groupCallbacks++ - }) +// // alix can send messages +// await alixGroup.send('hello, world') +// await alixGroup.send('gm') + +// // bo's num groups == 1 +// await boClient.conversations.sync() +// boGroups = await boClient.conversations.listGroups() +// if (boGroups.length !== 1) { +// throw new Error( +// 'num groups for bo should be 1, but it is' + boGroups.length +// ) +// } - await bo.conversations.streamAllMessages(async () => { - messageCallbacks++ - }) +// await alixGroup.addMembers([caroClient.address]) - const group = await alix.conversations.newGroup([bo.address]) - await group.send('hello') +// // caro's num groups == 1 +// await caroClient.conversations.sync() +// caroGroups = await caroClient.conversations.listGroups() +// if (caroGroups.length !== 1) { +// throw new Error( +// 'num groups for caro should be 1, but it is' + caroGroups.length +// ) +// } +// await caroGroups[0].sync() +// const caroMessages = await caroGroups[0].messages() +// if (caroMessages.length !== 0) { +// throw new Error('num messages for caro should be 0') +// } - await delayToPropogate() - // await new Promise((resolve) => setTimeout(resolve, 10000)) - assert( - messageCallbacks === 1, - 'message stream should have received 1 message' - ) - assert(groupCallbacks === 1, 'group stream should have received 1 group') - return true -}) +// await boGroups[0].sync() +// const boGroupMembers = await boGroups[0].memberInboxIds() +// if (boGroupMembers.length !== 3) { +// throw new Error('num group members should be 3') +// } -test('can stream groups', async () => { - const [alixClient, boClient, caroClient] = await createClients(3) - - // Start streaming groups - const groups: Conversation[] = [] - await alixClient.conversations.stream(async (group: Conversation) => { - groups.push(group) - }, 'groups') - - // caro creates a group with alix, so stream callback is fired - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const caroGroup = await caroClient.conversations.newGroup([ - alixClient.address, - ]) - await delayToPropogate() - if ((groups.length as number) !== 1) { - throw Error('Unexpected num groups (should be 1): ' + groups.length) - } +// return true +// }) - assert((await groups[0].members()).length === 2, 'should be 2') +// test('can remove members from a group', async () => { +// // Create three MLS enabled Clients +// const [alixClient, boClient, caroClient] = await createClients(3) - // bo creates a group with alix so a stream callback is fired - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const boGroup = await boClient.conversations.newGroup([alixClient.address]) - await delayToPropogate() - if ((groups.length as number) !== 2) { - throw Error('Unexpected num groups (should be 2): ' + groups.length) - } +// // alix's num groups start at 0 +// let alixGroups = await alixClient.conversations.listGroups() +// if (alixGroups.length !== 0) { +// throw new Error('num groups should be 0') +// } - // * Note alix creating a group does not trigger alix conversations - // group stream. Workaround is to sync after you create and list manually - // See https://github.com/xmtp/libxmtp/issues/504 - - // alix creates a group - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const alixGroup = await alixClient.conversations.newGroup([ - boClient.address, - caroClient.address, - ]) - await delayToPropogate() - if (groups.length !== 3) { - throw Error('Expected group length 3 but it is: ' + groups.length) - } - // Sync groups after creation if you created a group - const listedGroups = await alixClient.conversations.listGroups() - await delayToPropogate() - groups.push(listedGroups[listedGroups.length - 1]) - if ((groups.length as number) !== 4) { - throw Error('Expected group length 4 but it is: ' + groups.length) - } +// // bo's num groups start at 0 +// let boGroups = await boClient.conversations.listGroups() +// assert(boGroups.length === 0, 'num groups should be 0') - alixClient.conversations.cancelStream() - await delayToPropogate() - - // Creating a group should no longer trigger stream groups - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const caroSecond = await caroClient.conversations.newGroup([ - alixClient.address, - ]) - await delayToPropogate() - if ((groups.length as number) !== 4) { - throw Error('Unexpected num groups (should be 4): ' + groups.length) - } +// // caro's num groups start at 0 +// let caroGroups = await caroClient.conversations.listGroups() +// if (caroGroups.length !== 0) { +// throw new Error('num groups should be 0') +// } - return true -}) +// // alix creates a group +// const alixGroup = await alixClient.conversations.newGroup([ +// boClient.address, +// caroClient.address, +// ]) + +// // alix's num groups == 1 +// await alixClient.conversations.sync() +// alixGroups = await alixClient.conversations.listGroups() +// if (alixGroups.length !== 1) { +// throw new Error('num groups should be 1') +// } -test('can filter groups by consent', async () => { - const [alixClient, boClient, caroClient] = await createClients(3) - - const boGroup1 = await boClient.conversations.newGroup([alixClient.address]) - const otherGroup = await alixClient.conversations.newGroup([boClient.address]) - await boClient.conversations.findOrCreateDm(alixClient.address) - await caroClient.conversations.findOrCreateDm(boClient.address) - await boClient.conversations.sync() - await boClient.conversations.findDmByInboxId(caroClient.inboxId) - const boGroup2 = await boClient.conversations.findGroup(otherGroup.id) - - const boConvos = await boClient.conversations.listGroups() - const boConvosFilteredAllowed = await boClient.conversations.listGroups( - {}, - undefined, - ['allowed'] - ) - const boConvosFilteredUnknown = await boClient.conversations.listGroups( - {}, - undefined, - ['unknown'] - ) +// // alix can confirm memberInboxIds +// await alixGroup.sync() +// const memberInboxIds = await alixGroup.memberInboxIds() +// if (memberInboxIds.length !== 3) { +// throw new Error('num group members should be 3') +// } +// if ( +// !( +// memberInboxIds.includes(alixClient.inboxId) && +// memberInboxIds.includes(boClient.inboxId) +// ) +// ) { +// throw new Error('missing address') +// } - assert( - boConvos.length === 2, - `Conversation length should be 2 but was ${boConvos.length}` - ) +// // alix can send messages +// await alixGroup.send('hello, world') +// await alixGroup.send('gm') + +// // bo's num groups == 1 +// await boClient.conversations.sync() +// boGroups = await boClient.conversations.listGroups() +// if (boGroups.length !== 1) { +// throw new Error( +// 'num groups for bo should be 1, but it is' + boGroups.length +// ) +// } - assert( - boConvosFilteredAllowed - .map((conversation: any) => conversation.id) - .toString() === [boGroup1.id].toString(), - `Conversation allowed should be ${[ - boGroup1.id, - ].toString()} but was ${boConvosFilteredAllowed - .map((convo: any) => convo.id) - .toString()}` - ) +// // caro's num groups == 1 +// await caroClient.conversations.sync() +// caroGroups = await caroClient.conversations.listGroups() +// if (caroGroups.length !== 1) { +// throw new Error( +// 'num groups for caro should be 1, but it is' + caroGroups.length +// ) +// } - assert( - boConvosFilteredUnknown - .map((conversation: any) => conversation.id) - .toString() === [boGroup2?.id].toString(), - `Conversation unknown filter should be ${[ - boGroup2?.id, - ].toString()} but was ${boConvosFilteredUnknown - .map((convo: any) => convo.id) - .toString()}` - ) +// await caroGroups[0].sync() +// if (!caroGroups[0].isActive()) { +// throw new Error('caros group should be active') +// } - return true -}) +// await alixGroup.removeMembers([caroClient.address]) +// await alixGroup.sync() +// const alixGroupMembers = await alixGroup.memberInboxIds() +// if (alixGroupMembers.length !== 2) { +// throw new Error( +// 'num group members should be 2 but was' + alixGroupMembers.length +// ) +// } -test('can list groups with params', async () => { - const [alixClient, boClient] = await createClients(2) +// await caroGroups[0].sync() +// if (await caroGroups[0].isActive()) { +// throw new Error('caros group should not be active') +// } - const boGroup1 = await boClient.conversations.newGroup([alixClient.address]) - const boGroup2 = await boClient.conversations.newGroup([alixClient.address]) +// const caroGroupMembers = await caroGroups[0].memberInboxIds() +// if (caroGroupMembers.length !== 2) { +// throw new Error( +// 'num group members should be 2 but was' + caroGroupMembers.length +// ) +// } - await boGroup1.send({ text: `first message` }) - await boGroup1.send({ text: `second message` }) - await boGroup1.send({ text: `third message` }) - await boGroup2.send({ text: `first message` }) +// return true +// }) - const boGroupsOrderLastMessage = await boClient.conversations.listGroups({ - lastMessage: true, - }) - const boGroupsLimit = await boClient.conversations.listGroups({}, 1) +// test('can remove and add members from a group by inbox id', async () => { +// // Create three MLS enabled Clients +// const [alixClient, boClient, caroClient] = await createClients(3) + +// // alix creates a group +// const alixGroup = await alixClient.conversations.newGroup([ +// boClient.address, +// caroClient.address, +// ]) + +// // alix can confirm memberInboxIds +// await alixGroup.sync() +// const memberInboxIds = await alixGroup.memberInboxIds() +// if (memberInboxIds.length !== 3) { +// throw new Error('num group members should be 3') +// } - assert( - boGroupsOrderLastMessage.map((group: any) => group.id).toString() === - [boGroup2.id, boGroup1.id].toString(), - `Group order should be group2 then group1 but was ${boGroupsOrderLastMessage - .map((group: any) => group.id) - .toString()}` - ) +// await alixGroup.removeMembersByInboxId([caroClient.inboxId]) +// await alixGroup.sync() +// const alixGroupMembers = await alixGroup.memberInboxIds() +// if (alixGroupMembers.length !== 2) { +// throw new Error('num group members should be 2') +// } - const messages = await boGroupsOrderLastMessage[0].messages() - assert( - messages[0].content() === 'first message', - `last message should be first message ${messages[0].content()}` - ) - assert( - boGroupsOrderLastMessage[0].lastMessage?.content() === 'first message', - `last message should be last message ${boGroupsOrderLastMessage[0].lastMessage?.content()}` - ) - assert( - boGroupsLimit.length === 1, - `List length should be 1 but was ${boGroupsLimit.length}` - ) +// await alixGroup.addMembersByInboxId([caroClient.inboxId]) +// await alixGroup.sync() +// const alixGroupMembers2 = await alixGroup.memberInboxIds() +// if (alixGroupMembers2.length !== 3) { +// throw new Error('num group members should be 3') +// } - return true -}) +// return true +// }) -test('can list groups', async () => { - const [alixClient, boClient] = await createClients(2) +// test('can stream both groups and messages at same time', async () => { +// const [alix, bo] = await createClients(2) - const group1 = await boClient.conversations.newGroup([alixClient.address], { - name: 'group1 name', - imageUrlSquare: 'www.group1image.com', - }) - const group2 = await boClient.conversations.newGroup([alixClient.address], { - name: 'group2 name', - imageUrlSquare: 'www.group2image.com', - }) +// let groupCallbacks = 0 +// let messageCallbacks = 0 +// await bo.conversations.stream(async () => { +// groupCallbacks++ +// }) - const boGroups = await boClient.conversations.listGroups() - await alixClient.conversations.sync() - const alixGroups = await alixClient.conversations.listGroups() +// await bo.conversations.streamAllMessages(async () => { +// messageCallbacks++ +// }) - assert( - boGroups.length === alixGroups.length, - `group lengths should be the same but bo was ${boGroups.length} and alix was ${alixGroups.length}` - ) +// const group = await alix.conversations.newGroup([bo.address]) +// await group.send('hello') - const boGroup1 = await boClient.conversations.findGroup(group1.id) - const boGroup2 = await boClient.conversations.findGroup(group2.id) +// await delayToPropogate() +// // await new Promise((resolve) => setTimeout(resolve, 10000)) +// assert( +// messageCallbacks === 1, +// 'message stream should have received 1 message' +// ) +// assert(groupCallbacks === 1, 'group stream should have received 1 group') +// return true +// }) - const alixGroup1 = await alixClient.conversations.findGroup(group1.id) - const alixGroup2 = await alixClient.conversations.findGroup(group2.id) +// test('can stream groups', async () => { +// const [alixClient, boClient, caroClient] = await createClients(3) - assert( - boGroup2?.name === 'group2 name', - `Group 2 name for bo should be group2 name but was ${boGroup2?.name}` - ) +// // Start streaming groups +// const groups: Conversation[] = [] +// await alixClient.conversations.stream(async (group: Conversation) => { +// groups.push(group) +// }, 'groups') - assert( - boGroup1?.imageUrlSquare === 'www.group1image.com', - `Group 2 url for bo should be www.group1image.com but was ${boGroup1?.imageUrlSquare}` - ) +// // caro creates a group with alix, so stream callback is fired +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const caroGroup = await caroClient.conversations.newGroup([ +// alixClient.address, +// ]) +// await delayToPropogate() +// if ((groups.length as number) !== 1) { +// throw Error('Unexpected num groups (should be 1): ' + groups.length) +// } - assert( - alixGroup1?.name === 'group1 name', - `Group 1 name for alix should be group1 name but was ${alixGroup1?.name}` - ) +// assert((await groups[0].members()).length === 2, 'should be 2') - assert( - alixGroup2?.imageUrlSquare === 'www.group2image.com', - `Group 2 url for alix should be www.group2image.com but was ${alixGroup2?.imageUrlSquare}` - ) +// // bo creates a group with alix so a stream callback is fired +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const boGroup = await boClient.conversations.newGroup([alixClient.address]) +// await delayToPropogate() +// if ((groups.length as number) !== 2) { +// throw Error('Unexpected num groups (should be 2): ' + groups.length) +// } - assert(boGroup1?.isGroupActive === true, `Group 1 should be active for bo`) +// // * Note alix creating a group does not trigger alix conversations +// // group stream. Workaround is to sync after you create and list manually +// // See https://github.com/xmtp/libxmtp/issues/504 - return true -}) +// // alix creates a group +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const alixGroup = await alixClient.conversations.newGroup([ +// boClient.address, +// caroClient.address, +// ]) +// await delayToPropogate() +// if (groups.length !== 3) { +// throw Error('Expected group length 3 but it is: ' + groups.length) +// } +// // Sync groups after creation if you created a group +// const listedGroups = await alixClient.conversations.listGroups() +// await delayToPropogate() +// groups.push(listedGroups[listedGroups.length - 1]) +// if ((groups.length as number) !== 4) { +// throw Error('Expected group length 4 but it is: ' + groups.length) +// } -test('can stream groups and messages', async () => { - const [alixClient, boClient] = await createClients(2) +// alixClient.conversations.cancelStream() +// await delayToPropogate() - // Start streaming groups - const groups: Conversation[] = [] - await alixClient.conversations.stream(async (group: Conversation) => { - groups.push(group) - }) - // Stream messages twice - await alixClient.conversations.streamAllMessages(async (message) => {}) - await alixClient.conversations.streamAllMessages(async (message) => {}) - - // bo creates a group with alix so a stream callback is fired - // eslint-disable-next-line @typescript-eslint/no-unused-vars - await boClient.conversations.newGroup([alixClient.address]) - await delayToPropogate() - if ((groups.length as number) !== 1) { - throw Error(`Unexpected num groups (should be 1): ${groups.length}`) - } +// // Creating a group should no longer trigger stream groups +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const caroSecond = await caroClient.conversations.newGroup([ +// alixClient.address, +// ]) +// await delayToPropogate() +// if ((groups.length as number) !== 4) { +// throw Error('Unexpected num groups (should be 4): ' + groups.length) +// } - return true -}) +// return true +// }) -test('canMessage', async () => { - const [alix, caro] = await createClients(3) +// test('can filter groups by consent', async () => { +// const [alixClient, boClient, caroClient] = await createClients(3) + +// const boGroup1 = await boClient.conversations.newGroup([alixClient.address]) +// const otherGroup = await alixClient.conversations.newGroup([boClient.address]) +// await boClient.conversations.findOrCreateDm(alixClient.address) +// await caroClient.conversations.findOrCreateDm(boClient.address) +// await boClient.conversations.sync() +// await boClient.conversations.findDmByInboxId(caroClient.inboxId) +// const boGroup2 = await boClient.conversations.findGroup(otherGroup.id) + +// const boConvos = await boClient.conversations.listGroups() +// const boConvosFilteredAllowed = await boClient.conversations.listGroups( +// {}, +// undefined, +// ['allowed'] +// ) +// const boConvosFilteredUnknown = await boClient.conversations.listGroups( +// {}, +// undefined, +// ['unknown'] +// ) + +// assert( +// boConvos.length === 2, +// `Conversation length should be 2 but was ${boConvos.length}` +// ) + +// assert( +// boConvosFilteredAllowed +// .map((conversation: any) => conversation.id) +// .toString() === [boGroup1.id].toString(), +// `Conversation allowed should be ${[ +// boGroup1.id, +// ].toString()} but was ${boConvosFilteredAllowed +// .map((convo: any) => convo.id) +// .toString()}` +// ) + +// assert( +// boConvosFilteredUnknown +// .map((conversation: any) => conversation.id) +// .toString() === [boGroup2?.id].toString(), +// `Conversation unknown filter should be ${[ +// boGroup2?.id, +// ].toString()} but was ${boConvosFilteredUnknown +// .map((convo: any) => convo.id) +// .toString()}` +// ) - const canMessageV3 = await caro.canMessage([ - caro.address, - alix.address, - '0x4E9ce36E442e55EcD9025B9a6E0D88485d628A67', - ]) +// return true +// }) - assert( - canMessageV3['0x4E9ce36E442e55EcD9025B9a6E0D88485d628A67'.toLowerCase()] === - false, - `should not be able to message 0x4E9ce36E442e55EcD9025B9a6E0D88485d628A67` - ) +// test('can list groups with params', async () => { +// const [alixClient, boClient] = await createClients(2) + +// const boGroup1 = await boClient.conversations.newGroup([alixClient.address]) +// const boGroup2 = await boClient.conversations.newGroup([alixClient.address]) + +// await boGroup1.send({ text: `first message` }) +// await boGroup1.send({ text: `second message` }) +// await boGroup1.send({ text: `third message` }) +// await boGroup2.send({ text: `first message` }) + +// const boGroupsOrderLastMessage = await boClient.conversations.listGroups({ +// lastMessage: true, +// }) +// const boGroupsLimit = await boClient.conversations.listGroups({}, 1) + +// assert( +// boGroupsOrderLastMessage.map((group: any) => group.id).toString() === +// [boGroup2.id, boGroup1.id].toString(), +// `Group order should be group2 then group1 but was ${boGroupsOrderLastMessage +// .map((group: any) => group.id) +// .toString()}` +// ) + +// const messages = await boGroupsOrderLastMessage[0].messages() +// assert( +// messages[0].content() === 'first message', +// `last message should be first message ${messages[0].content()}` +// ) +// assert( +// boGroupsOrderLastMessage[0].lastMessage?.content() === 'first message', +// `last message should be last message ${boGroupsOrderLastMessage[0].lastMessage?.content()}` +// ) +// assert( +// boGroupsLimit.length === 1, +// `List length should be 1 but was ${boGroupsLimit.length}` +// ) - assert( - canMessageV3[caro.address.toLowerCase()] === true, - `should be able to message ${caro.address}` - ) +// return true +// }) - assert( - canMessageV3[alix.address.toLowerCase()] === true, - `should be able to message ${alix.address}` - ) +// test('can list groups', async () => { +// const [alixClient, boClient] = await createClients(2) - return true -}) +// const group1 = await boClient.conversations.newGroup([alixClient.address], { +// name: 'group1 name', +// imageUrlSquare: 'www.group1image.com', +// }) +// const group2 = await boClient.conversations.newGroup([alixClient.address], { +// name: 'group2 name', +// imageUrlSquare: 'www.group2image.com', +// }) -test('can stream group messages', async () => { - // Create three MLS enabled Clients - const [alixClient, boClient, caroClient] = await createClients(3) - - // alix creates a group - const alixGroup = await alixClient.conversations.newGroup([ - boClient.address, - caroClient.address, - ]) - - // Record message stream for this group - const groupMessages: DecodedMessage[] = [] - const cancelGroupMessageStream = await alixGroup.streamMessages( - async (message) => { - groupMessages.push(message) - } - ) +// const boGroups = await boClient.conversations.listGroups() +// await alixClient.conversations.sync() +// const alixGroups = await alixClient.conversations.listGroups() - // bo's num groups == 1 - await boClient.conversations.sync() - const boGroup = (await boClient.conversations.listGroups())[0] +// assert( +// boGroups.length === alixGroups.length, +// `group lengths should be the same but bo was ${boGroups.length} and alix was ${alixGroups.length}` +// ) - for (let i = 0; i < 5; i++) { - await boGroup.send({ text: `Message ${i}` }) - await delayToPropogate() - } +// const boGroup1 = await boClient.conversations.findGroup(group1.id) +// const boGroup2 = await boClient.conversations.findGroup(group2.id) - if (groupMessages.length !== 5) { - throw Error('Unexpected convo messages count ' + groupMessages.length) - } - for (let i = 0; i < 5; i++) { - if (groupMessages[i].content() !== `Message ${i}`) { - throw Error( - 'Unexpected group message content ' + groupMessages[i].content() - ) - } - } +// const alixGroup1 = await alixClient.conversations.findGroup(group1.id) +// const alixGroup2 = await alixClient.conversations.findGroup(group2.id) - cancelGroupMessageStream() - for (let i = 0; i < 5; i++) { - await boGroup.send({ text: `Message ${i}` }) - } +// assert( +// boGroup2?.name === 'group2 name', +// `Group 2 name for bo should be group2 name but was ${boGroup2?.name}` +// ) - if (groupMessages.length !== 5) { - throw Error('Unexpected convo messages count ' + groupMessages.length) - } +// assert( +// boGroup1?.imageUrlSquare === 'www.group1image.com', +// `Group 2 url for bo should be www.group1image.com but was ${boGroup1?.imageUrlSquare}` +// ) - return true -}) +// assert( +// alixGroup1?.name === 'group1 name', +// `Group 1 name for alix should be group1 name but was ${alixGroup1?.name}` +// ) -test('can make a group with metadata', async () => { - const [alix, bo] = await createClients(2) - Client.register(new GroupUpdatedCodec()) +// assert( +// alixGroup2?.imageUrlSquare === 'www.group2image.com', +// `Group 2 url for alix should be www.group2image.com but was ${alixGroup2?.imageUrlSquare}` +// ) - const alixGroup = await alix.conversations.newGroup([bo.address], { - name: 'Start Name', - imageUrlSquare: 'starturl.com', - description: 'a fun description', - }) +// assert(boGroup1?.isGroupActive === true, `Group 1 should be active for bo`) - const groupName1 = await alixGroup.groupName() - const groupImageUrl1 = await alixGroup.groupImageUrlSquare() - const groupDescription1 = await alixGroup.groupDescription() - assert( - groupName1 === 'Start Name', - `the group should start with a name of Start Name not ${groupName1}` - ) +// return true +// }) - assert( - groupImageUrl1 === 'starturl.com', - `the group should start with a name of starturl.com not ${groupImageUrl1}` - ) +// test('can stream groups and messages', async () => { +// const [alixClient, boClient] = await createClients(2) + +// // Start streaming groups +// const groups: Conversation[] = [] +// await alixClient.conversations.stream(async (group: Conversation) => { +// groups.push(group) +// }) +// // Stream messages twice +// await alixClient.conversations.streamAllMessages(async (message) => {}) +// await alixClient.conversations.streamAllMessages(async (message) => {}) + +// // bo creates a group with alix so a stream callback is fired +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// await boClient.conversations.newGroup([alixClient.address]) +// await delayToPropogate() +// if ((groups.length as number) !== 1) { +// throw Error(`Unexpected num groups (should be 1): ${groups.length}`) +// } - assert( - groupDescription1 === 'a fun description', - `the group should start with a name of a fun description not ${groupDescription1}` - ) +// return true +// }) - await alixGroup.updateGroupName('New Name') - await alixGroup.updateGroupImageUrlSquare('newurl.com') - await alixGroup.updateGroupDescription('a new group description') - await alixGroup.sync() - await bo.conversations.sync() - const boGroups = await bo.conversations.listGroups() - const boGroup = boGroups[0] - await boGroup.sync() +// test('canMessage', async () => { +// const [alix, caro] = await createClients(3) - const groupName2 = await alixGroup.groupName() - const groupImageUrl2 = await alixGroup.groupImageUrlSquare() - const groupDescription2 = await alixGroup.groupDescription() - assert( - groupName2 === 'New Name', - `the group should start with a name of New Name not ${groupName2}` - ) +// const canMessageV3 = await caro.canMessage([ +// caro.address, +// alix.address, +// '0x4E9ce36E442e55EcD9025B9a6E0D88485d628A67', +// ]) - assert( - groupImageUrl2 === 'newurl.com', - `the group should start with a name of newurl.com not ${groupImageUrl2}` - ) +// assert( +// canMessageV3['0x4E9ce36E442e55EcD9025B9a6E0D88485d628A67'.toLowerCase()] === +// false, +// `should not be able to message 0x4E9ce36E442e55EcD9025B9a6E0D88485d628A67` +// ) - assert( - groupDescription2 === 'a new group description', - `the group should start with a name of a new group description not ${groupDescription2}` - ) +// assert( +// canMessageV3[caro.address.toLowerCase()] === true, +// `should be able to message ${caro.address}` +// ) - const groupName3 = await boGroup.groupName() - const groupImageUrl3 = await boGroup.groupImageUrlSquare() - assert( - groupName3 === 'New Name', - `the group should start with a name of New Name not ${groupName3}` - ) +// assert( +// canMessageV3[alix.address.toLowerCase()] === true, +// `should be able to message ${alix.address}` +// ) - assert( - groupImageUrl3 === 'newurl.com', - `the group should start with a name of newurl.com not ${groupImageUrl3}` - ) +// return true +// }) - const boMessages = await boGroup.messages() - assert( - boMessages[0].contentTypeId === 'xmtp.org/group_updated:1.0', - 'Unexpected message content ' + JSON.stringify(boMessages[0].contentTypeId) - ) +// test('can stream group messages', async () => { +// // Create three MLS enabled Clients +// const [alixClient, boClient, caroClient] = await createClients(3) + +// // alix creates a group +// const alixGroup = await alixClient.conversations.newGroup([ +// boClient.address, +// caroClient.address, +// ]) + +// // Record message stream for this group +// const groupMessages: DecodedMessage[] = [] +// const cancelGroupMessageStream = await alixGroup.streamMessages( +// async (message) => { +// groupMessages.push(message) +// } +// ) + +// // bo's num groups == 1 +// await boClient.conversations.sync() +// const boGroup = (await boClient.conversations.listGroups())[0] + +// for (let i = 0; i < 5; i++) { +// await boGroup.send({ text: `Message ${i}` }) +// await delayToPropogate() +// } - const message = boMessages[1].content() as GroupUpdatedContent - assert( - message.metadataFieldsChanged[0].fieldName === 'group_image_url_square', - `the metadata field changed should be group_image_url_square but was ${message.metadataFieldsChanged[0].fieldName}` - ) - const message2 = boMessages[0].content() as GroupUpdatedContent - assert( - message2.metadataFieldsChanged[0].fieldName === 'description', - `the metadata field changed should be description but was ${message2.metadataFieldsChanged[0].fieldName}` - ) - return true -}) +// if (groupMessages.length !== 5) { +// throw Error('Unexpected convo messages count ' + groupMessages.length) +// } +// for (let i = 0; i < 5; i++) { +// if (groupMessages[i].content() !== `Message ${i}`) { +// throw Error( +// 'Unexpected group message content ' + groupMessages[i].content() +// ) +// } +// } -test('can make a group with admin permissions', async () => { - const [adminClient, anotherClient] = await createClients(2) +// cancelGroupMessageStream() +// for (let i = 0; i < 5; i++) { +// await boGroup.send({ text: `Message ${i}` }) +// } - const group = await adminClient.conversations.newGroup( - [anotherClient.address], - { permissionLevel: 'admin_only' } - ) +// if (groupMessages.length !== 5) { +// throw Error('Unexpected convo messages count ' + groupMessages.length) +// } - if ((await group.permissionPolicySet()).addMemberPolicy !== 'admin') { - throw Error( - `Group permission level should be admin but was ${ - (await group.permissionPolicySet()).addMemberPolicy - }` - ) - } +// return true +// }) - const isSuperAdmin = await group.isSuperAdmin(adminClient.inboxId) - if (!isSuperAdmin) { - throw Error(`adminClient should be the super admin`) - } +// test('can make a group with metadata', async () => { +// const [alix, bo] = await createClients(2) +// Client.register(new GroupUpdatedCodec()) + +// const alixGroup = await alix.conversations.newGroup([bo.address], { +// name: 'Start Name', +// imageUrlSquare: 'starturl.com', +// description: 'a fun description', +// }) + +// const groupName1 = await alixGroup.groupName() +// const groupImageUrl1 = await alixGroup.groupImageUrlSquare() +// const groupDescription1 = await alixGroup.groupDescription() +// assert( +// groupName1 === 'Start Name', +// `the group should start with a name of Start Name not ${groupName1}` +// ) + +// assert( +// groupImageUrl1 === 'starturl.com', +// `the group should start with a name of starturl.com not ${groupImageUrl1}` +// ) + +// assert( +// groupDescription1 === 'a fun description', +// `the group should start with a name of a fun description not ${groupDescription1}` +// ) + +// await alixGroup.updateGroupName('New Name') +// await alixGroup.updateGroupImageUrlSquare('newurl.com') +// await alixGroup.updateGroupDescription('a new group description') +// await alixGroup.sync() +// await bo.conversations.sync() +// const boGroups = await bo.conversations.listGroups() +// const boGroup = boGroups[0] +// await boGroup.sync() + +// const groupName2 = await alixGroup.groupName() +// const groupImageUrl2 = await alixGroup.groupImageUrlSquare() +// const groupDescription2 = await alixGroup.groupDescription() +// assert( +// groupName2 === 'New Name', +// `the group should start with a name of New Name not ${groupName2}` +// ) + +// assert( +// groupImageUrl2 === 'newurl.com', +// `the group should start with a name of newurl.com not ${groupImageUrl2}` +// ) + +// assert( +// groupDescription2 === 'a new group description', +// `the group should start with a name of a new group description not ${groupDescription2}` +// ) + +// const groupName3 = await boGroup.groupName() +// const groupImageUrl3 = await boGroup.groupImageUrlSquare() +// assert( +// groupName3 === 'New Name', +// `the group should start with a name of New Name not ${groupName3}` +// ) + +// assert( +// groupImageUrl3 === 'newurl.com', +// `the group should start with a name of newurl.com not ${groupImageUrl3}` +// ) + +// const boMessages = await boGroup.messages() +// assert( +// boMessages[0].contentTypeId === 'xmtp.org/group_updated:1.0', +// 'Unexpected message content ' + JSON.stringify(boMessages[0].contentTypeId) +// ) + +// const message = boMessages[1].content() as GroupUpdatedContent +// assert( +// message.metadataFieldsChanged[0].fieldName === 'group_image_url_square', +// `the metadata field changed should be group_image_url_square but was ${message.metadataFieldsChanged[0].fieldName}` +// ) +// const message2 = boMessages[0].content() as GroupUpdatedContent +// assert( +// message2.metadataFieldsChanged[0].fieldName === 'description', +// `the metadata field changed should be description but was ${message2.metadataFieldsChanged[0].fieldName}` +// ) +// return true +// }) - // Creator id not working, see https://github.com/xmtp/libxmtp/issues/788 - // if (group.creatorInboxId !== adminClient.inboxId) { - // throw Error( - // `adminClient should be the creator but was ${group.creatorInboxId}` - // ) - // } +// test('can make a group with admin permissions', async () => { +// const [adminClient, anotherClient] = await createClients(2) - return true -}) +// const group = await adminClient.conversations.newGroup( +// [anotherClient.address], +// { permissionLevel: 'admin_only' } +// ) -test('can paginate group messages', async () => { - // Create three MLS enabled Clients - const [alixClient, boClient] = await createClients(2) +// if ((await group.permissionPolicySet()).addMemberPolicy !== 'admin') { +// throw Error( +// `Group permission level should be admin but was ${ +// (await group.permissionPolicySet()).addMemberPolicy +// }` +// ) +// } - // alix creates a group - const alixGroup = await alixClient.conversations.newGroup([boClient.address]) +// const isSuperAdmin = await group.isSuperAdmin(adminClient.inboxId) +// if (!isSuperAdmin) { +// throw Error(`adminClient should be the super admin`) +// } - // alix can send messages - await alixGroup.send('hello, world') - await alixGroup.send('gm') +// // Creator id not working, see https://github.com/xmtp/libxmtp/issues/788 +// // if (group.creatorInboxId !== adminClient.inboxId) { +// // throw Error( +// // `adminClient should be the creator but was ${group.creatorInboxId}` +// // ) +// // } - await boClient.conversations.sync() - const boGroups = await boClient.conversations.listGroups() - if (boGroups.length !== 1) { - throw new Error( - 'num groups for bo should be 1, but it is' + boGroups.length - ) - } - await delayToPropogate() - // bo can read messages from alix - await boGroups[0].sync() - const boMessages: DecodedMessage[] = await boGroups[0].messages({ - limit: 1, - }) +// return true +// }) - if (boMessages.length !== 1) { - throw Error(`Should limit just 1 message but was ${boMessages.length}`) - } +// test('can paginate group messages', async () => { +// // Create three MLS enabled Clients +// const [alixClient, boClient] = await createClients(2) - return true -}) +// // alix creates a group +// const alixGroup = await alixClient.conversations.newGroup([boClient.address]) -test('can stream all group messages', async () => { - const [alix, bo, caro] = await createClients(3) +// // alix can send messages +// await alixGroup.send('hello, world') +// await alixGroup.send('gm') - await delayToPropogate() +// await boClient.conversations.sync() +// const boGroups = await boClient.conversations.listGroups() +// if (boGroups.length !== 1) { +// throw new Error( +// 'num groups for bo should be 1, but it is' + boGroups.length +// ) +// } +// await delayToPropogate() +// // bo can read messages from alix +// await boGroups[0].sync() +// const boMessages: DecodedMessage[] = await boGroups[0].messages({ +// limit: 1, +// }) + +// if (boMessages.length !== 1) { +// throw Error(`Should limit just 1 message but was ${boMessages.length}`) +// } - // Start bo starts a new group. - const boGroup = await bo.conversations.newGroup([alix.address]) - await delayToPropogate() +// return true +// }) - // Starts a new conversation. - const caroGroup = await caro.conversations.newGroup([alix.address]) +// test('can stream all group messages', async () => { +// const [alix, bo, caro] = await createClients(3) - // Record message stream across all conversations - const allMessages: DecodedMessage[] = [] - // If we don't call syncConversations here, the streamAllGroupMessages will not - // stream the first message. Feels like a bug. - await alix.conversations.sync() - await alix.conversations.streamAllMessages(async (message) => { - allMessages.push(message) - }, 'groups') +// await delayToPropogate() - for (let i = 0; i < 5; i++) { - await boGroup.send({ text: `Message ${i}` }) - await delayToPropogate() - } +// // Start bo starts a new group. +// const boGroup = await bo.conversations.newGroup([alix.address]) +// await delayToPropogate() - const count = allMessages.length - if (count !== 5) { - throw Error('Unexpected all messages count first' + allMessages.length) - } +// // Starts a new conversation. +// const caroGroup = await caro.conversations.newGroup([alix.address]) + +// // Record message stream across all conversations +// const allMessages: DecodedMessage[] = [] +// // If we don't call syncConversations here, the streamAllGroupMessages will not +// // stream the first message. Feels like a bug. +// await alix.conversations.sync() +// await alix.conversations.streamAllMessages(async (message) => { +// allMessages.push(message) +// }, 'groups') + +// for (let i = 0; i < 5; i++) { +// await boGroup.send({ text: `Message ${i}` }) +// await delayToPropogate() +// } - await delayToPropogate() - for (let i = 0; i < 5; i++) { - await caroGroup.send({ text: `Message ${i}` }) - await delayToPropogate() - } +// const count = allMessages.length +// if (count !== 5) { +// throw Error('Unexpected all messages count first' + allMessages.length) +// } - if (allMessages.length !== 10) { - throw Error('Unexpected all messages count second' + allMessages.length) - } +// await delayToPropogate() +// for (let i = 0; i < 5; i++) { +// await caroGroup.send({ text: `Message ${i}` }) +// await delayToPropogate() +// } - alix.conversations.cancelStreamAllMessages() - await delayToPropogate() - await alix.conversations.streamAllMessages(async (message) => { - allMessages.push(message) - }) +// if (allMessages.length !== 10) { +// throw Error('Unexpected all messages count second' + allMessages.length) +// } - for (let i = 0; i < 5; i++) { - await boGroup.send({ text: `Message ${i}` }) - await delayToPropogate() - } - if (allMessages.length <= 10) { - throw Error('Unexpected all messages count ' + allMessages.length) - } +// alix.conversations.cancelStreamAllMessages() +// await delayToPropogate() +// await alix.conversations.streamAllMessages(async (message) => { +// allMessages.push(message) +// }) - return true -}) +// for (let i = 0; i < 5; i++) { +// await boGroup.send({ text: `Message ${i}` }) +// await delayToPropogate() +// } +// if (allMessages.length <= 10) { +// throw Error('Unexpected all messages count ' + allMessages.length) +// } -test('creating a group should allow group', async () => { - const [alix, bo] = await createClients(2) +// return true +// }) - const group = await alix.conversations.newGroup([bo.address]) - await alix.conversations.sync() - const consent = await alix.preferences.conversationConsentState(group.id) - const groupConsent = await group.consentState() +// test('creating a group should allow group', async () => { +// const [alix, bo] = await createClients(2) - if (consent !== groupConsent) { - throw Error('Group should be allowed') - } +// const group = await alix.conversations.newGroup([bo.address]) +// await alix.conversations.sync() +// const consent = await alix.preferences.conversationConsentState(group.id) +// const groupConsent = await group.consentState() - assert( - groupConsent === 'allowed', - `the message should have a consent state of allowed but was ${groupConsent}` - ) +// if (consent !== groupConsent) { +// throw Error('Group should be allowed') +// } - return true -}) +// assert( +// groupConsent === 'allowed', +// `the message should have a consent state of allowed but was ${groupConsent}` +// ) -test('can group consent', async () => { - const [alix, bo] = await createClients(2) - const group = await bo.conversations.newGroup([alix.address]) - await alix.conversations.sync() - let isAllowed = await alix.preferences.conversationConsentState(group.id) - assert( - isAllowed !== 'allowed', - `alix group should NOT be allowed but was ${isAllowed}` - ) +// return true +// }) - isAllowed = await bo.preferences.conversationConsentState(group.id) - assert( - isAllowed === 'allowed', - `bo group should be allowed but was ${isAllowed}` - ) - assert( - (await group.state) === 'allowed', - `the group should have a consent state of allowed but was ${await group.state}` - ) +// test('can group consent', async () => { +// const [alix, bo] = await createClients(2) +// const group = await bo.conversations.newGroup([alix.address]) +// await alix.conversations.sync() +// let isAllowed = await alix.preferences.conversationConsentState(group.id) +// assert( +// isAllowed !== 'allowed', +// `alix group should NOT be allowed but was ${isAllowed}` +// ) + +// isAllowed = await bo.preferences.conversationConsentState(group.id) +// assert( +// isAllowed === 'allowed', +// `bo group should be allowed but was ${isAllowed}` +// ) +// assert( +// (await group.state) === 'allowed', +// `the group should have a consent state of allowed but was ${await group.state}` +// ) + +// await bo.preferences.setConsentState( +// new ConsentRecord(group.id, 'conversation_id', 'denied') +// ) +// const isDenied = await bo.preferences.conversationConsentState(group.id) +// assert(isDenied === 'denied', `bo group should be denied but was ${isDenied}`) +// assert( +// (await group.consentState()) === 'denied', +// `the group should have a consent state of denied but was ${await group.consentState()}` +// ) + +// await group.updateConsent('allowed') +// isAllowed = await bo.preferences.conversationConsentState(group.id) +// assert( +// isAllowed === 'allowed', +// `bo group should be allowed2 but was ${isAllowed}` +// ) +// assert( +// (await group.consentState()) === 'allowed', +// `the group should have a consent state2 of allowed but was ${await group.consentState()}` +// ) - await bo.preferences.setConsentState( - new ConsentRecord(group.id, 'conversation_id', 'denied') - ) - const isDenied = await bo.preferences.conversationConsentState(group.id) - assert(isDenied === 'denied', `bo group should be denied but was ${isDenied}`) - assert( - (await group.consentState()) === 'denied', - `the group should have a consent state of denied but was ${await group.consentState()}` - ) +// return true +// }) - await group.updateConsent('allowed') - isAllowed = await bo.preferences.conversationConsentState(group.id) - assert( - isAllowed === 'allowed', - `bo group should be allowed2 but was ${isAllowed}` - ) - assert( - (await group.consentState()) === 'allowed', - `the group should have a consent state2 of allowed but was ${await group.consentState()}` - ) +// test('can allow and deny a inbox id', async () => { +// const [alix, bo] = await createClients(2) +// const boGroup = await bo.conversations.newGroup([alix.address]) + +// let isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) +// assert( +// isInboxAllowed === 'unknown', +// `isInboxAllowed should be unknown but was ${isInboxAllowed}` +// ) + +// await bo.preferences.setConsentState( +// new ConsentRecord(alix.inboxId, 'inbox_id', 'allowed') +// ) + +// let alixMember = (await boGroup.members()).find( +// (member) => member.inboxId === alix.inboxId +// ) +// assert( +// alixMember?.consentState === 'allowed', +// `alixMember should be allowed but was ${alixMember?.consentState}` +// ) + +// isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) +// assert( +// isInboxAllowed === 'allowed', +// `isInboxAllowed2 should be true but was ${isInboxAllowed}` +// ) + +// let isAddressAllowed = await bo.preferences.addressConsentState(alix.address) +// assert( +// isAddressAllowed === 'allowed', +// `isAddressAllowed should be true but was ${isAddressAllowed}` +// ) + +// await bo.preferences.setConsentState( +// new ConsentRecord(alix.inboxId, 'inbox_id', 'denied') +// ) + +// alixMember = (await boGroup.members()).find( +// (member) => member.inboxId === alix.inboxId +// ) +// assert( +// alixMember?.consentState === 'denied', +// `alixMember should be denied but was ${alixMember?.consentState}` +// ) + +// isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) +// assert( +// isInboxAllowed === 'denied', +// `isInboxAllowed3 should be false but was ${isInboxAllowed}` +// ) + +// await bo.preferences.setConsentState( +// new ConsentRecord(alix.address, 'address', 'allowed') +// ) + +// isAddressAllowed = await bo.preferences.addressConsentState(alix.address) +// assert( +// isAddressAllowed === 'allowed', +// `isAddressAllowed2 should be true but was ${isAddressAllowed}` +// ) +// isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) +// assert( +// isInboxAllowed === 'allowed', +// `isInboxAllowed4 should be false but was ${isInboxAllowed}` +// ) - return true -}) +// return true +// }) -test('can allow and deny a inbox id', async () => { - const [alix, bo] = await createClients(2) - const boGroup = await bo.conversations.newGroup([alix.address]) +// test('sync function behaves as expected', async () => { +// const [alix, bo, caro] = await createClients(3) +// const alixGroup = await alix.conversations.newGroup([bo.address]) - let isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) - assert( - isInboxAllowed === 'unknown', - `isInboxAllowed should be unknown but was ${isInboxAllowed}` - ) +// await alixGroup.send({ text: 'hello' }) - await bo.preferences.setConsentState( - new ConsentRecord(alix.inboxId, 'inbox_id', 'allowed') - ) +// // List groups will return empty until the first sync +// let boGroups = await bo.conversations.listGroups() +// assert(boGroups.length === 0, 'num groups for bo is 0 until we sync') - let alixMember = (await boGroup.members()).find( - (member) => member.inboxId === alix.inboxId - ) - assert( - alixMember?.consentState === 'allowed', - `alixMember should be allowed but was ${alixMember?.consentState}` - ) +// await bo.conversations.sync() - isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) - assert( - isInboxAllowed === 'allowed', - `isInboxAllowed2 should be true but was ${isInboxAllowed}` - ) +// boGroups = await bo.conversations.listGroups() +// assert(boGroups.length === 1, 'num groups for bo is 1') - let isAddressAllowed = await bo.preferences.addressConsentState(alix.address) - assert( - isAddressAllowed === 'allowed', - `isAddressAllowed should be true but was ${isAddressAllowed}` - ) +// // Num members will include the initial num of members even before sync +// let numMembers = (await boGroups[0].memberInboxIds()).length +// assert(numMembers === 2, 'num members should be 2') - await bo.preferences.setConsentState( - new ConsentRecord(alix.inboxId, 'inbox_id', 'denied') - ) +// // Num messages for a group will be 0 until we sync the group +// let numMessages = (await boGroups[0].messages()).length +// assert(numMessages === 0, 'num members should be 1') - alixMember = (await boGroup.members()).find( - (member) => member.inboxId === alix.inboxId - ) - assert( - alixMember?.consentState === 'denied', - `alixMember should be denied but was ${alixMember?.consentState}` - ) +// await bo.conversations.sync() - isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) - assert( - isInboxAllowed === 'denied', - `isInboxAllowed3 should be false but was ${isInboxAllowed}` - ) +// // Num messages is still 0 because we didnt sync the group itself +// numMessages = (await boGroups[0].messages()).length +// assert(numMessages === 0, 'num messages should be 0') - await bo.preferences.setConsentState( - new ConsentRecord(alix.address, 'address', 'allowed') - ) +// await boGroups[0].sync() - isAddressAllowed = await bo.preferences.addressConsentState(alix.address) - assert( - isAddressAllowed === 'allowed', - `isAddressAllowed2 should be true but was ${isAddressAllowed}` - ) - isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) - assert( - isInboxAllowed === 'allowed', - `isInboxAllowed4 should be false but was ${isInboxAllowed}` - ) +// // after syncing the group we now see the correct number of messages +// numMessages = (await boGroups[0].messages()).length +// assert(numMessages === 1, 'num members should be 1') - return true -}) +// await alixGroup.addMembers([caro.address]) -test('sync function behaves as expected', async () => { - const [alix, bo, caro] = await createClients(3) - const alixGroup = await alix.conversations.newGroup([bo.address]) +// numMembers = (await boGroups[0].memberInboxIds()).length +// assert(numMembers === 2, 'num members should be 2') - await alixGroup.send({ text: 'hello' }) +// await bo.conversations.sync() - // List groups will return empty until the first sync - let boGroups = await bo.conversations.listGroups() - assert(boGroups.length === 0, 'num groups for bo is 0 until we sync') +// // Even though we synced the groups, we need to sync the group itself to see the new member +// numMembers = (await boGroups[0].memberInboxIds()).length +// assert(numMembers === 2, 'num members should be 2') - await bo.conversations.sync() +// await boGroups[0].sync() - boGroups = await bo.conversations.listGroups() - assert(boGroups.length === 1, 'num groups for bo is 1') +// numMembers = (await boGroups[0].memberInboxIds()).length +// assert(numMembers === 3, 'num members should be 3') - // Num members will include the initial num of members even before sync - let numMembers = (await boGroups[0].memberInboxIds()).length - assert(numMembers === 2, 'num members should be 2') +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const _alixGroup2 = await alix.conversations.newGroup([ +// bo.address, +// caro.address, +// ]) +// await bo.conversations.sync() +// boGroups = await bo.conversations.listGroups() +// assert(boGroups.length === 2, 'num groups for bo is 2') - // Num messages for a group will be 0 until we sync the group - let numMessages = (await boGroups[0].messages()).length - assert(numMessages === 0, 'num members should be 1') +// // Even before syncing the group, sync will return the initial number of members +// numMembers = (await boGroups[1].memberInboxIds()).length +// assert(numMembers === 3, 'num members should be 3') - await bo.conversations.sync() +// return true +// }) - // Num messages is still 0 because we didnt sync the group itself - numMessages = (await boGroups[0].messages()).length - assert(numMessages === 0, 'num messages should be 0') +// test('can read and update group name', async () => { +// const [alix, bo, caro] = await createClients(3) +// const alixGroup = await alix.conversations.newGroup([bo.address]) - await boGroups[0].sync() +// await alixGroup.sync() +// let groupName = await alixGroup.groupName() - // after syncing the group we now see the correct number of messages - numMessages = (await boGroups[0].messages()).length - assert(numMessages === 1, 'num members should be 1') +// assert(groupName === '', 'group name should be empty string') - await alixGroup.addMembers([caro.address]) +// await alixGroup.updateGroupName('Test name update 1') - numMembers = (await boGroups[0].memberInboxIds()).length - assert(numMembers === 2, 'num members should be 2') +// await alixGroup.sync() +// groupName = await alixGroup.groupName() - await bo.conversations.sync() +// assert( +// groupName === 'Test name update 1', +// 'group name should be "Test name update 1"' +// ) - // Even though we synced the groups, we need to sync the group itself to see the new member - numMembers = (await boGroups[0].memberInboxIds()).length - assert(numMembers === 2, 'num members should be 2') +// await bo.conversations.sync() +// const boGroup = (await bo.conversations.listGroups())[0] +// groupName = await boGroup.groupName() - await boGroups[0].sync() +// assert(groupName === '', 'group name should be empty string') - numMembers = (await boGroups[0].memberInboxIds()).length - assert(numMembers === 3, 'num members should be 3') +// await boGroup.sync() - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const _alixGroup2 = await alix.conversations.newGroup([ - bo.address, - caro.address, - ]) - await bo.conversations.sync() - boGroups = await bo.conversations.listGroups() - assert(boGroups.length === 2, 'num groups for bo is 2') +// groupName = await boGroup.groupName() - // Even before syncing the group, sync will return the initial number of members - numMembers = (await boGroups[1].memberInboxIds()).length - assert(numMembers === 3, 'num members should be 3') +// assert( +// groupName === 'Test name update 1', +// 'group name should be "Test name update 1"' +// ) - return true -}) +// await alixGroup.addMembers([caro.address]) +// await caro.conversations.sync() +// const caroGroup = (await caro.conversations.listGroups())[0] -test('can read and update group name', async () => { - const [alix, bo, caro] = await createClients(3) - const alixGroup = await alix.conversations.newGroup([bo.address]) +// await caroGroup.sync() +// groupName = await caroGroup.groupName() +// assert( +// groupName === 'Test name update 1', +// 'group name should be "Test name update 1"' +// ) +// return true +// }) - await alixGroup.sync() - let groupName = await alixGroup.groupName() +// test('can list groups does not fork', async () => { +// const [alix, bo] = await createClients(2) +// console.log('created clients') +// let groupCallbacks = 0 +// //#region Stream groups +// await bo.conversations.stream(async () => { +// console.log('group received') +// groupCallbacks++ +// }) +// //#region Stream All Messages +// await bo.conversations.streamAllMessages(async () => { +// console.log('message received') +// }) +// //#endregion +// // #region create group +// const alixGroup = await alix.conversations.newGroup([bo.address]) +// await alixGroup.updateGroupName('hello') +// await alixGroup.send('hello1') +// console.log('sent group message') +// // #endregion +// // #region sync groups +// await bo.conversations.sync() +// // #endregion +// const boGroups = await bo.conversations.listGroups() +// assert(boGroups.length === 1, 'bo should have 1 group') +// const boGroup = boGroups[0] +// await boGroup.sync() + +// const boMessages1 = await boGroup.messages() +// assert( +// boMessages1.length === 2, +// `should have 2 messages on first load received ${boMessages1.length}` +// ) +// await boGroup.send('hello2') +// await boGroup.send('hello3') +// await alixGroup.sync() +// const alixMessages = await alixGroup.messages() +// for (const message of alixMessages) { +// console.log( +// 'message', +// message.contentTypeId, +// message.contentTypeId === 'xmtp.org/text:1.0' +// ? message.content() +// : 'Group Updated' +// ) +// } +// // alix sees 3 messages +// assert( +// alixMessages.length === 5, +// `should have 5 messages on first load received ${alixMessages.length}` +// ) +// await alixGroup.send('hello4') +// await boGroup.sync() +// const boMessages2 = await boGroup.messages() +// for (const message of boMessages2) { +// console.log( +// 'message', +// message.contentTypeId, +// message.contentTypeId === 'xmtp.org/text:1.0' +// ? message.content() +// : 'Group Updated' +// ) +// } +// // bo sees 4 messages +// assert( +// boMessages2.length === 5, +// `should have 5 messages on second load received ${boMessages2.length}` +// ) - assert(groupName === '', 'group name should be empty string') +// await delayToPropogate(500) - await alixGroup.updateGroupName('Test name update 1') +// assert(groupCallbacks === 1, 'group stream should have received 1 group') - await alixGroup.sync() - groupName = await alixGroup.groupName() +// return true +// }) - assert( - groupName === 'Test name update 1', - 'group name should be "Test name update 1"' - ) +// test('can list many groups members in parallel', async () => { +// const [alix, bo] = await createClients(2) +// const groups: Group[] = await createGroups(alix, [bo], 20) - await bo.conversations.sync() - const boGroup = (await bo.conversations.listGroups())[0] - groupName = await boGroup.groupName() +// try { +// await Promise.all(groups.slice(0, 10).map((g) => g.members())) +// } catch (e) { +// throw new Error(`Failed listing 10 groups members with ${e}`) +// } - assert(groupName === '', 'group name should be empty string') +// try { +// await Promise.all(groups.slice(0, 20).map((g) => g.members())) +// } catch (e) { +// throw new Error(`Failed listing 20 groups members with ${e}`) +// } - await boGroup.sync() +// return true +// }) - groupName = await boGroup.groupName() +// test('can sync all groups', async () => { +// const [alix, bo] = await createClients(2) +// const groups: Group[] = await createGroups(alix, [bo], 50) + +// const alixGroup = groups[0] +// await bo.conversations.sync() +// const boGroup = await bo.conversations.findGroup(alixGroup.id) +// await alixGroup.send('hi') +// assert( +// (await boGroup?.messages())?.length === 0, +// `messages should be empty before sync but was ${boGroup?.messages?.length}` +// ) + +// const numGroupsSynced = await bo.conversations.syncAllConversations() +// assert( +// (await boGroup?.messages())?.length === 1, +// `messages should be 4 after sync but was ${boGroup?.messages?.length}` +// ) +// assert( +// numGroupsSynced === 51, +// `should have synced 51 groups but synced ${numGroupsSynced}` +// ) + +// for (const group of groups) { +// await group.removeMembers([bo.address]) +// } - assert( - groupName === 'Test name update 1', - 'group name should be "Test name update 1"' - ) +// // First syncAllConversations after removal will still sync each group to set group inactive +// const numGroupsSynced2 = await bo.conversations.syncAllConversations() +// assert( +// numGroupsSynced2 === 51, +// `should have synced 51 groups but synced ${numGroupsSynced2}` +// ) + +// // Next syncAllConversations will not sync inactive groups +// const numGroupsSynced3 = await bo.conversations.syncAllConversations() +// assert( +// numGroupsSynced3 === 1, +// `should have synced 1 groups but synced ${numGroupsSynced3}` +// ) +// return true +// }) - await alixGroup.addMembers([caro.address]) - await caro.conversations.sync() - const caroGroup = (await caro.conversations.listGroups())[0] +// test('only streams groups that can be decrypted', async () => { +// const [alixClient, boClient, caroClient] = await createClients(3) +// const alixGroups: Conversation[] = [] +// const boGroups: Conversation[] = [] +// const caroGroups: Conversation[] = [] + +// await alixClient.conversations.stream(async (group: Conversation) => { +// alixGroups.push(group) +// }) +// await boClient.conversations.stream(async (group: Conversation) => { +// boGroups.push(group) +// }) +// await caroClient.conversations.stream(async (group: Conversation) => { +// caroGroups.push(group) +// }) + +// await alixClient.conversations.newGroup([boClient.address]) +// await delayToPropogate() +// assert( +// alixGroups.length === 1, +// `alix group length should be 1 but was ${alixGroups.length}` +// ) - await caroGroup.sync() - groupName = await caroGroup.groupName() - assert( - groupName === 'Test name update 1', - 'group name should be "Test name update 1"' - ) - return true -}) +// assert( +// boGroups.length === 1, +// `bo group length should be 1 but was ${boGroups.length}` +// ) -test('can list groups does not fork', async () => { - const [alix, bo] = await createClients(2) - console.log('created clients') - let groupCallbacks = 0 - //#region Stream groups - await bo.conversations.stream(async () => { - console.log('group received') - groupCallbacks++ - }) - //#region Stream All Messages - await bo.conversations.streamAllMessages(async () => { - console.log('message received') - }) - //#endregion - // #region create group - const alixGroup = await alix.conversations.newGroup([bo.address]) - await alixGroup.updateGroupName('hello') - await alixGroup.send('hello1') - console.log('sent group message') - // #endregion - // #region sync groups - await bo.conversations.sync() - // #endregion - const boGroups = await bo.conversations.listGroups() - assert(boGroups.length === 1, 'bo should have 1 group') - const boGroup = boGroups[0] - await boGroup.sync() +// assert( +// caroGroups.length !== 1, +// `caro group length should be 0 but was ${caroGroups.length}` +// ) - const boMessages1 = await boGroup.messages() - assert( - boMessages1.length === 2, - `should have 2 messages on first load received ${boMessages1.length}` - ) - await boGroup.send('hello2') - await boGroup.send('hello3') - await alixGroup.sync() - const alixMessages = await alixGroup.messages() - for (const message of alixMessages) { - console.log( - 'message', - message.contentTypeId, - message.contentTypeId === 'xmtp.org/text:1.0' - ? message.content() - : 'Group Updated' - ) - } - // alix sees 3 messages - assert( - alixMessages.length === 5, - `should have 5 messages on first load received ${alixMessages.length}` - ) - await alixGroup.send('hello4') - await boGroup.sync() - const boMessages2 = await boGroup.messages() - for (const message of boMessages2) { - console.log( - 'message', - message.contentTypeId, - message.contentTypeId === 'xmtp.org/text:1.0' - ? message.content() - : 'Group Updated' - ) - } - // bo sees 4 messages - assert( - boMessages2.length === 5, - `should have 5 messages on second load received ${boMessages2.length}` - ) +// return true +// }) - await delayToPropogate(500) +// test('can stream groups and messages', async () => { +// for (let index = 0; index < 15; index++) { +// console.log(`stream groups & messages: test ${index}`) +// const [alixClient, boClient] = await createClients(2) + +// // Start streaming groups +// const groups: Conversation[] = [] +// await alixClient.conversations.stream(async (group: Conversation) => { +// groups.push(group) +// }) +// // Stream messages twice +// await alixClient.conversations.streamAllMessages(async (message) => {}) +// await alixClient.conversations.streamAllMessages(async (message) => {}) + +// // bo creates a group with alix so a stream callback is fired +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// await boClient.conversations.newGroup([alixClient.address]) +// await delayToPropogate(500) +// if ((groups.length as number) !== 1) { +// throw Error(`Unexpected num groups (should be 1): ${groups.length}`) +// } +// } - assert(groupCallbacks === 1, 'group stream should have received 1 group') +// return true +// }) - return true -}) +// test('can create new installation without breaking group', async () => { +// 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, +// ]) +// const wallet1 = Wallet.createRandom() +// const wallet2 = Wallet.createRandom() + +// const client1 = await Client.create(adaptEthersWalletToSigner(wallet1), { +// env: 'local', +// appVersion: 'Testing/0.0.0', +// dbEncryptionKey: keyBytes, +// }) +// const client2 = await Client.create(adaptEthersWalletToSigner(wallet2), { +// env: 'local', +// appVersion: 'Testing/0.0.0', +// dbEncryptionKey: keyBytes, +// }) + +// const group = await client1.conversations.newGroup([wallet2.address]) + +// await client1.conversations.sync() +// await client2.conversations.sync() + +// const client1Group = await client1.conversations.findGroup(group.id) +// const client2Group = await client2.conversations.findGroup(group.id) + +// await client1Group?.sync() +// await client2Group?.sync() + +// const members1 = await client1Group?.members() +// assert( +// members1?.length === 2, +// `client 1 should see 2 members but was ${members1?.length}` +// ) + +// const members2 = await client2Group?.members() +// assert( +// members2?.length === 2, +// `client 2 should see 2 members but was ${members2?.length}` +// ) + +// await client2.deleteLocalDatabase() + +// // Recreating a client with wallet 2 (new installation!) +// await Client.create(adaptEthersWalletToSigner(wallet2), { +// env: 'local', +// appVersion: 'Testing/0.0.0', +// dbEncryptionKey: keyBytes, +// }) + +// await client1Group?.send('This message will break the group') +// const members3 = await client1Group?.members() +// assert( +// members3?.length === 2, +// `client 1 should still see the 2 members but was ${members3?.length}` +// ) -test('can list many groups members in parallel', async () => { - const [alix, bo] = await createClients(2) - const groups: Group[] = await createGroups(alix, [bo], 20) +// return true +// }) + +test('handles disappearing messages in a group', async () => { + const [alixClient, boClient] = await createClients(2) - try { - await Promise.all(groups.slice(0, 10).map((g) => g.members())) - } catch (e) { - throw new Error(`Failed listing 10 groups members with ${e}`) + const initialSettings = { + disappearStartingAtNs: 1_000_000_000, + retentionDurationInNs: 1_000_000_000, // 1s duration } - try { - await Promise.all(groups.slice(0, 20).map((g) => g.members())) - } catch (e) { - throw new Error(`Failed listing 20 groups members with ${e}`) + const customPermissionsPolicySet: PermissionPolicySet = { + addMemberPolicy: 'allow', + removeMemberPolicy: 'deny', + addAdminPolicy: 'admin', + removeAdminPolicy: 'superAdmin', + updateGroupNamePolicy: 'admin', + updateGroupDescriptionPolicy: 'allow', + updateGroupImagePolicy: 'admin', + updateMessageDisappearingPolicy: 'deny', } - return true -}) + // Create group with disappearing messages enabled + const boGroup = await boClient.conversations.newGroup([alixClient.address], { + disappearingMessageSettings: initialSettings, + }) + await boClient.conversations.newGroupWithInboxIds([alixClient.inboxId], { + disappearingMessageSettings: initialSettings, + }) + await boClient.conversations.newGroupCustomPermissions( + [alixClient.address], + customPermissionsPolicySet, + { + disappearingMessageSettings: initialSettings, + } + ) + await boClient.conversations.newGroupCustomPermissionsWithInboxIds( + [alixClient.inboxId], + customPermissionsPolicySet, + { + disappearingMessageSettings: initialSettings, + } + ) + await boGroup.send('howdy') + await alixClient.conversations.syncAllConversations() -test('can sync all groups', async () => { - const [alix, bo] = await createClients(2) - const groups: Group[] = await createGroups(alix, [bo], 50) + const alixGroup = await alixClient.conversations.findGroup(boGroup.id) - const alixGroup = groups[0] - await bo.conversations.sync() - const boGroup = await bo.conversations.findGroup(alixGroup.id) - await alixGroup.send('hi') + // Validate messages exist and settings are applied assert( - (await boGroup?.messages())?.length === 0, - `messages should be empty before sync but was ${boGroup?.messages?.length}` + (await boGroup.messages()).length === 2, + 'BoGroup should have 2 messages' + ) + assert( + (await alixGroup!.messages()).length === 1, + 'AlixGroup should have 1 message' ) - - const numGroupsSynced = await bo.conversations.syncAllConversations() assert( - (await boGroup?.messages())?.length === 1, - `messages should be 4 after sync but was ${boGroup?.messages?.length}` + (await boGroup.disappearingMessageSettings()) !== undefined, + `boGroup disappearing message was ${await boGroup.disappearingMessageSettings}` ) assert( - numGroupsSynced === 51, - `should have synced 51 groups but synced ${numGroupsSynced}` + (await boGroup.disappearingMessageSettings())!.retentionDurationInNs === + 1_000_000_000, + 'Should be 1s' + ) + assert( + (await boGroup.disappearingMessageSettings())!.disappearStartingAtNs === + 1_000_000_000, + 'Should be 1s' ) - for (const group of groups) { - await group.removeMembers([bo.address]) - } + await delayToPropogate(5000) - // First syncAllConversations after removal will still sync each group to set group inactive - const numGroupsSynced2 = await bo.conversations.syncAllConversations() + // Validate messages are deleted assert( - numGroupsSynced2 === 51, - `should have synced 51 groups but synced ${numGroupsSynced2}` + (await boGroup.messages()).length === 1, + `BoGroup should have 1 remaining message but was ${(await boGroup.messages()).length}` ) - - // Next syncAllConversations will not sync inactive groups - const numGroupsSynced3 = await bo.conversations.syncAllConversations() assert( - numGroupsSynced3 === 1, - `should have synced 1 groups but synced ${numGroupsSynced3}` + (await alixGroup!.messages()).length === 0, + 'AlixGroup should have 0 messages left' ) - return true -}) -test('only streams groups that can be decrypted', async () => { - const [alixClient, boClient, caroClient] = await createClients(3) - const alixGroups: Conversation[] = [] - const boGroups: Conversation[] = [] - const caroGroups: Conversation[] = [] - - await alixClient.conversations.stream(async (group: Conversation) => { - alixGroups.push(group) - }) - await boClient.conversations.stream(async (group: Conversation) => { - boGroups.push(group) - }) - await caroClient.conversations.stream(async (group: Conversation) => { - caroGroups.push(group) - }) + // Disable disappearing messages + await boGroup.clearDisappearingMessageSettings() + await boGroup.sync() + await alixGroup!.sync() - await alixClient.conversations.newGroup([boClient.address]) - await delayToPropogate() assert( - alixGroups.length === 1, - `alix group length should be 1 but was ${alixGroups.length}` + !(await boGroup.disappearingMessageSettings), + 'BoGroup should have no disappearing settings' ) - assert( - boGroups.length === 1, - `bo group length should be 1 but was ${boGroups.length}` + !(await alixGroup!.disappearingMessageSettings), + 'AlixGroup should have no disappearing settings' ) - assert( - caroGroups.length !== 1, - `caro group length should be 0 but was ${caroGroups.length}` + !boGroup.isDisappearingMessagesEnabled, + 'BoGroup should have disappearing disabled' + ) + assert( + !alixGroup!.isDisappearingMessagesEnabled, + 'AlixGroup should have disappearing disabled' ) - return true -}) - -test('can stream groups and messages', async () => { - for (let index = 0; index < 15; index++) { - console.log(`stream groups & messages: test ${index}`) - const [alixClient, boClient] = await createClients(2) - - // Start streaming groups - const groups: Conversation[] = [] - await alixClient.conversations.stream(async (group: Conversation) => { - groups.push(group) - }) - // Stream messages twice - await alixClient.conversations.streamAllMessages(async (message) => {}) - await alixClient.conversations.streamAllMessages(async (message) => {}) - - // bo creates a group with alix so a stream callback is fired - // eslint-disable-next-line @typescript-eslint/no-unused-vars - await boClient.conversations.newGroup([alixClient.address]) - await delayToPropogate(500) - if ((groups.length as number) !== 1) { - throw Error(`Unexpected num groups (should be 1): ${groups.length}`) - } - } + // Send messages after disabling disappearing settings + await boGroup.send('message after disabling disappearing') + await alixGroup!.send('another message after disabling') + await boGroup.sync() - return true -}) + await new Promise((resolve) => setTimeout(resolve, 1000)) -test('can create new installation without breaking group', async () => { - 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, - ]) - const wallet1 = Wallet.createRandom() - const wallet2 = Wallet.createRandom() - - const client1 = await Client.create(adaptEthersWalletToSigner(wallet1), { - env: 'local', - appVersion: 'Testing/0.0.0', - dbEncryptionKey: keyBytes, - }) - const client2 = await Client.create(adaptEthersWalletToSigner(wallet2), { - env: 'local', - appVersion: 'Testing/0.0.0', - dbEncryptionKey: keyBytes, - }) + // Ensure messages persist + assert( + (await boGroup.messages()).length === 5, + 'BoGroup should have 5 messages' + ) + assert( + (await alixGroup!.messages()).length === 4, + 'AlixGroup should have 4 messages' + ) - const group = await client1.conversations.newGroup([wallet2.address]) + // Re-enable disappearing messages + const updatedSettings = { + disappearStartingAtNs: (await boGroup.messages())[0].sentNs + 1_000_000_000, // 1s from now + retentionDurationInNs: 1_000_000_000, + } + await boGroup.updateDisappearingMessageSettings(updatedSettings) + await boGroup.sync() + await alixGroup!.sync() - await client1.conversations.sync() - await client2.conversations.sync() + await new Promise((resolve) => setTimeout(resolve, 1000)) - const client1Group = await client1.conversations.findGroup(group.id) - const client2Group = await client2.conversations.findGroup(group.id) + assert( + (await boGroup.disappearingMessageSettings())!.disappearStartingAtNs === + updatedSettings.disappearStartingAtNs, + `Should be ${updatedSettings.disappearStartingAtNs} but was ${await boGroup!.disappearingMessageSettings}` + ) + assert( + (await alixGroup!.disappearingMessageSettings())!.disappearStartingAtNs === + updatedSettings.disappearStartingAtNs, + `Should be ${updatedSettings.disappearStartingAtNs} but was ${await alixGroup!.disappearingMessageSettings}` + ) - await client1Group?.sync() - await client2Group?.sync() + // Send new messages + await boGroup.send('this will disappear soon') + await alixGroup!.send('so will this') + await boGroup.sync() - const members1 = await client1Group?.members() assert( - members1?.length === 2, - `client 1 should see 2 members but was ${members1?.length}` + (await boGroup.messages()).length === 9, + 'BoGroup should have 9 messages' ) - - const members2 = await client2Group?.members() assert( - members2?.length === 2, - `client 2 should see 2 members but was ${members2?.length}` + (await alixGroup!.messages()).length === 8, + 'AlixGroup should have 8 messages' ) - await client2.deleteLocalDatabase() + await delayToPropogate(6000) - // Recreating a client with wallet 2 (new installation!) - await Client.create(adaptEthersWalletToSigner(wallet2), { - env: 'local', - appVersion: 'Testing/0.0.0', - dbEncryptionKey: keyBytes, - }) + // Validate messages were deleted + assert( + (await boGroup.messages()).length === 7, + 'BoGroup should have 7 messages left' + ) + assert( + (await alixGroup!.messages()).length === 6, + 'AlixGroup should have 6 messages left' + ) - await client1Group?.send('This message will break the group') - const members3 = await client1Group?.members() + // Final validation that settings persist + assert( + (await boGroup.disappearingMessageSettings())!.retentionDurationInNs === + updatedSettings.retentionDurationInNs, + `Should be ${updatedSettings.disappearStartingAtNs} but was ${await boGroup.disappearingMessageSettings}` + ) + assert( + (await alixGroup!.disappearingMessageSettings())!.retentionDurationInNs === + updatedSettings.retentionDurationInNs, + `Should be ${updatedSettings.disappearStartingAtNs} but was ${await alixGroup!.disappearingMessageSettings}` + ) + assert( + await boGroup.isDisappearingMessagesEnabled(), + 'BoGroup should have disappearing enabled' + ) assert( - members3?.length === 2, - `client 1 should still see the 2 members but was ${members3?.length}` + await alixGroup!.isDisappearingMessagesEnabled(), + 'AlixGroup should have disappearing enabled' ) return true From 321797f4e4a6e45d3d707754b93384e8a7ef7215 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Sun, 16 Feb 2025 21:16:56 -0800 Subject: [PATCH 16/20] more tests --- example/src/tests/clientTests.ts | 45 ++++++++ example/src/tests/groupTests.ts | 170 +++++++++++++++++++------------ example/src/tests/test-utils.ts | 8 ++ 3 files changed, 158 insertions(+), 65 deletions(-) diff --git a/example/src/tests/clientTests.ts b/example/src/tests/clientTests.ts index 397bb405..4b67e507 100644 --- a/example/src/tests/clientTests.ts +++ b/example/src/tests/clientTests.ts @@ -435,6 +435,51 @@ test('can verify signatures', async () => { return true }) +test('can add and remove accounts', async () => { + const [alixClient] = 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, + ]) + + const boWallet = Wallet.createRandom() + + const boClient = await Client.create(adaptEthersWalletToSigner(boWallet), { + env: 'local', + appVersion: 'Testing/0.0.0', + dbEncryptionKey: keyBytes, + }) + + let errorThrown = false + try { + await alixClient.addAccount(adaptEthersWalletToSigner(boWallet)) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + errorThrown = true + } + + if (!errorThrown) { + throw new Error('Expected addAccount to throw an error but it did not') + } + + // Ensure that both clients have different inbox IDs + expect(alixClient.inboxId).not.toBe(boClient.inboxId) + + // Forcefully add the boClient account to alixClient + await alixClient.addAccount(adaptEthersWalletToSigner(boWallet), true) + + // Retrieve the inbox state and check the number of associated addresses + const state = await alixClient.inboxState(true) + expect(state.addresses.length).toBe(2) + + // Validate that the inbox ID from the address matches alixClient's inbox ID + const inboxId = await alixClient.findInboxIdFromAddress(boClient.address) + expect(inboxId).toBe(alixClient.inboxId) + + return true +}) + test('can add and remove accounts', async () => { const keyBytes = new Uint8Array([ 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index 44e37357..f331506d 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -8,6 +8,7 @@ import { createGroups, delayToPropogate, adaptEthersWalletToSigner, + assertEqual, } from './test-utils' import { Client, @@ -2055,44 +2056,57 @@ test('handles disappearing messages in a group', async () => { disappearingMessageSettings: initialSettings, } ) + await boGroup.send('howdy') await alixClient.conversations.syncAllConversations() const alixGroup = await alixClient.conversations.findGroup(boGroup.id) - // Validate messages exist and settings are applied - assert( - (await boGroup.messages()).length === 2, + // Validate initial state + await assertEqual( + () => boGroup.messages().then((m) => m.length), + 2, 'BoGroup should have 2 messages' ) - assert( - (await alixGroup!.messages()).length === 1, + await assertEqual( + () => alixGroup!.messages().then((m) => m.length), + 1, 'AlixGroup should have 1 message' ) - assert( - (await boGroup.disappearingMessageSettings()) !== undefined, - `boGroup disappearing message was ${await boGroup.disappearingMessageSettings}` + await assertEqual( + () => boGroup.disappearingMessageSettings() !== undefined, + true, + 'BoGroup should have disappearing settings' ) - assert( - (await boGroup.disappearingMessageSettings())!.retentionDurationInNs === - 1_000_000_000, - 'Should be 1s' + await assertEqual( + () => + boGroup + .disappearingMessageSettings() + .then((s) => s!.retentionDurationInNs), + 1_000_000_000, + 'Retention duration should be 1s' ) - assert( - (await boGroup.disappearingMessageSettings())!.disappearStartingAtNs === - 1_000_000_000, - 'Should be 1s' + await assertEqual( + () => + boGroup + .disappearingMessageSettings() + .then((s) => s!.disappearStartingAtNs), + 1_000_000_000, + 'Disappearing should start at 1s' ) + // Wait for messages to disappear await delayToPropogate(5000) // Validate messages are deleted - assert( - (await boGroup.messages()).length === 1, - `BoGroup should have 1 remaining message but was ${(await boGroup.messages()).length}` + await assertEqual( + () => boGroup.messages().then((m) => m.length), + 1, + 'BoGroup should have 1 remaining message' ) - assert( - (await alixGroup!.messages()).length === 0, + await assertEqual( + () => alixGroup!.messages().then((m) => m.length), + 0, 'AlixGroup should have 0 messages left' ) @@ -2101,20 +2115,25 @@ test('handles disappearing messages in a group', async () => { await boGroup.sync() await alixGroup!.sync() - assert( - !(await boGroup.disappearingMessageSettings), - 'BoGroup should have no disappearing settings' + // Validate disappearing messages are disabled + await assertEqual( + () => boGroup.disappearingMessageSettings(), + undefined, + 'BoGroup should not have disappearing settings' ) - assert( - !(await alixGroup!.disappearingMessageSettings), - 'AlixGroup should have no disappearing settings' + await assertEqual( + () => alixGroup!.disappearingMessageSettings(), + undefined, + 'AlixGroup should not have disappearing settings' ) - assert( - !boGroup.isDisappearingMessagesEnabled, + await assertEqual( + () => boGroup.isDisappearingMessagesEnabled, + false, 'BoGroup should have disappearing disabled' ) - assert( - !alixGroup!.isDisappearingMessagesEnabled, + await assertEqual( + () => alixGroup!.isDisappearingMessagesEnabled, + false, 'AlixGroup should have disappearing disabled' ) @@ -2123,15 +2142,17 @@ test('handles disappearing messages in a group', async () => { await alixGroup!.send('another message after disabling') await boGroup.sync() - await new Promise((resolve) => setTimeout(resolve, 1000)) + await delayToPropogate(1000) // Ensure messages persist - assert( - (await boGroup.messages()).length === 5, + await assertEqual( + () => boGroup.messages().then((m) => m.length), + 5, 'BoGroup should have 5 messages' ) - assert( - (await alixGroup!.messages()).length === 4, + await assertEqual( + () => alixGroup!.messages().then((m) => m.length), + 4, 'AlixGroup should have 4 messages' ) @@ -2144,17 +2165,24 @@ test('handles disappearing messages in a group', async () => { await boGroup.sync() await alixGroup!.sync() - await new Promise((resolve) => setTimeout(resolve, 1000)) + await delayToPropogate(1000) - assert( - (await boGroup.disappearingMessageSettings())!.disappearStartingAtNs === - updatedSettings.disappearStartingAtNs, - `Should be ${updatedSettings.disappearStartingAtNs} but was ${await boGroup!.disappearingMessageSettings}` + // Validate updated settings + await assertEqual( + () => + boGroup + .disappearingMessageSettings() + .then((s) => s!.disappearStartingAtNs), + updatedSettings.disappearStartingAtNs, + 'BoGroup disappearStartingAtNs should match updated settings' ) - assert( - (await alixGroup!.disappearingMessageSettings())!.disappearStartingAtNs === - updatedSettings.disappearStartingAtNs, - `Should be ${updatedSettings.disappearStartingAtNs} but was ${await alixGroup!.disappearingMessageSettings}` + await assertEqual( + () => + alixGroup! + .disappearingMessageSettings() + .then((s) => s!.disappearStartingAtNs), + updatedSettings.disappearStartingAtNs, + 'AlixGroup disappearStartingAtNs should match updated settings' ) // Send new messages @@ -2162,44 +2190,56 @@ test('handles disappearing messages in a group', async () => { await alixGroup!.send('so will this') await boGroup.sync() - assert( - (await boGroup.messages()).length === 9, + await assertEqual( + () => boGroup.messages().then((m) => m.length), + 9, 'BoGroup should have 9 messages' ) - assert( - (await alixGroup!.messages()).length === 8, + await assertEqual( + () => alixGroup!.messages().then((m) => m.length), + 8, 'AlixGroup should have 8 messages' ) await delayToPropogate(6000) // Validate messages were deleted - assert( - (await boGroup.messages()).length === 7, + await assertEqual( + () => boGroup.messages().then((m) => m.length), + 7, 'BoGroup should have 7 messages left' ) - assert( - (await alixGroup!.messages()).length === 6, + await assertEqual( + () => alixGroup!.messages().then((m) => m.length), + 6, 'AlixGroup should have 6 messages left' ) // Final validation that settings persist - assert( - (await boGroup.disappearingMessageSettings())!.retentionDurationInNs === - updatedSettings.retentionDurationInNs, - `Should be ${updatedSettings.disappearStartingAtNs} but was ${await boGroup.disappearingMessageSettings}` + await assertEqual( + () => + boGroup + .disappearingMessageSettings() + .then((s) => s!.retentionDurationInNs), + updatedSettings.retentionDurationInNs, + 'BoGroup retentionDuration should match updated settings' ) - assert( - (await alixGroup!.disappearingMessageSettings())!.retentionDurationInNs === - updatedSettings.retentionDurationInNs, - `Should be ${updatedSettings.disappearStartingAtNs} but was ${await alixGroup!.disappearingMessageSettings}` + await assertEqual( + () => + alixGroup! + .disappearingMessageSettings() + .then((s) => s!.retentionDurationInNs), + updatedSettings.retentionDurationInNs, + 'AlixGroup retentionDuration should match updated settings' ) - assert( - await boGroup.isDisappearingMessagesEnabled(), + await assertEqual( + () => boGroup.isDisappearingMessagesEnabled(), + true, 'BoGroup should have disappearing enabled' ) - assert( - await alixGroup!.isDisappearingMessagesEnabled(), + await assertEqual( + () => alixGroup!.isDisappearingMessagesEnabled(), + true, 'AlixGroup should have disappearing enabled' ) diff --git a/example/src/tests/test-utils.ts b/example/src/tests/test-utils.ts index 766e661e..6d7c9121 100644 --- a/example/src/tests/test-utils.ts +++ b/example/src/tests/test-utils.ts @@ -84,3 +84,11 @@ export function adaptEthersWalletToSigner(wallet: Wallet): Signer { signMessage: async (message: string) => wallet.signMessage(message), } } + +export async function assertEqual(actual: any, expected: any, message: string) { + const resolvedActual = typeof actual === 'function' ? await actual() : actual; + assert( + resolvedActual === expected, + `${message} Expected: ${expected}, but was: ${resolvedActual}` + ) +} From 69d3e10ce4bb463e3594235eeb0b2032ff879398 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 17 Feb 2025 07:47:12 -0800 Subject: [PATCH 17/20] add test for existing inbodIds --- example/src/tests/clientTests.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/example/src/tests/clientTests.ts b/example/src/tests/clientTests.ts index 4b67e507..f82495aa 100644 --- a/example/src/tests/clientTests.ts +++ b/example/src/tests/clientTests.ts @@ -6,6 +6,7 @@ import { assert, createClients, adaptEthersWalletToSigner, + assertEqual, } from './test-utils' import { Client } from '../../../src/index' @@ -435,7 +436,7 @@ test('can verify signatures', async () => { return true }) -test('can add and remove accounts', async () => { +test('test add account with existing InboxIds', async () => { const [alixClient] = await createClients(1) const keyBytes = new Uint8Array([ @@ -464,18 +465,21 @@ test('can add and remove accounts', async () => { } // Ensure that both clients have different inbox IDs - expect(alixClient.inboxId).not.toBe(boClient.inboxId) + assert( + alixClient.inboxId !== boClient.inboxId, + 'Inbox ids should not be equal' + ) // Forcefully add the boClient account to alixClient await alixClient.addAccount(adaptEthersWalletToSigner(boWallet), true) // Retrieve the inbox state and check the number of associated addresses const state = await alixClient.inboxState(true) - expect(state.addresses.length).toBe(2) + await assertEqual(state.addresses.length, 2, 'Length should be 2') // Validate that the inbox ID from the address matches alixClient's inbox ID const inboxId = await alixClient.findInboxIdFromAddress(boClient.address) - expect(inboxId).toBe(alixClient.inboxId) + await assertEqual(inboxId, alixClient.inboxId, 'InboxIds should be equal') return true }) From cb4fc2693fae3b9c43a68012f50d767a20b0ed4b Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 17 Feb 2025 17:12:03 -0800 Subject: [PATCH 18/20] update tests --- example/src/tests/dmTests.ts | 217 +- example/src/tests/groupTests.ts | 3533 ++++++++++++++++--------------- example/src/tests/test-utils.ts | 3 +- src/index.ts | 12 +- 4 files changed, 1996 insertions(+), 1769 deletions(-) diff --git a/example/src/tests/dmTests.ts b/example/src/tests/dmTests.ts index ce5b92b7..eb6a44f7 100644 --- a/example/src/tests/dmTests.ts +++ b/example/src/tests/dmTests.ts @@ -1,4 +1,10 @@ -import { Test, assert, createClients, delayToPropogate } from './test-utils' +import { + Test, + assert, + assertEqual, + createClients, + delayToPropogate, +} from './test-utils' import { Conversation } from '../../../src/index' export const dmTests: Test[] = [] @@ -223,3 +229,212 @@ test('can stream all dms', async () => { return true }) + +test('handles disappearing messages in a dm', async () => { + const [alixClient, boClient, caroClient] = await createClients(3) + + const initialSettings = { + disappearStartingAtNs: 1_000_000_000, + retentionDurationInNs: 1_000_000_000, // 1s duration + } + + // Create group with disappearing messages enabled + const boDm = await boClient.conversations.findOrCreateDm( + alixClient.address, + initialSettings + ) + + await boClient.conversations.findOrCreateDmWithInboxId( + caroClient.inboxId, + initialSettings + ) + + await boDm.send('howdy') + await alixClient.conversations.syncAllConversations() + + const alixDm = await alixClient.conversations.findDmByInboxId( + boClient.inboxId + ) + + // Validate initial state + await assertEqual( + () => boDm.messages().then((m) => m.length), + 2, + 'boDm should have 2 messages' + ) + await assertEqual( + () => alixDm!.messages().then((m) => m.length), + 1, + 'alixDm should have 1 message' + ) + await assertEqual( + () => boDm.disappearingMessageSettings() !== undefined, + true, + 'boDm should have disappearing settings' + ) + await assertEqual( + () => + boDm.disappearingMessageSettings().then((s) => s!.retentionDurationInNs), + 1_000_000_000, + 'Retention duration should be 1s' + ) + await assertEqual( + () => + boDm.disappearingMessageSettings().then((s) => s!.disappearStartingAtNs), + 1_000_000_000, + 'Disappearing should start at 1s' + ) + + // Wait for messages to disappear + await delayToPropogate(5000) + + // Validate messages are deleted + await assertEqual( + () => boDm.messages().then((m) => m.length), + 1, + 'boDm should have 1 remaining message' + ) + await assertEqual( + () => alixDm!.messages().then((m) => m.length), + 0, + 'alixDm should have 0 messages left' + ) + + // Disable disappearing messages + await boDm.clearDisappearingMessageSettings() + await delayToPropogate(1000) + + await boDm.sync() + await alixDm!.sync() + + await delayToPropogate(1000) + + // Validate disappearing messages are disabled + await assertEqual( + () => boDm.disappearingMessageSettings(), + undefined, + 'boDm should not have disappearing settings' + ) + await assertEqual( + () => alixDm!.disappearingMessageSettings(), + undefined, + 'alixDm should not have disappearing settings' + ) + + await assertEqual( + () => boDm.isDisappearingMessagesEnabled(), + false, + 'boDm should have disappearing disabled' + ) + await assertEqual( + () => alixDm!.isDisappearingMessagesEnabled(), + false, + 'alixDm should have disappearing disabled' + ) + + // Send messages after disabling disappearing settings + await boDm.send('message after disabling disappearing') + await alixDm!.send('another message after disabling') + await boDm.sync() + + await delayToPropogate(1000) + + // Ensure messages persist + await assertEqual( + () => boDm.messages().then((m) => m.length), + 5, + 'boDm should have 5 messages' + ) + await assertEqual( + () => alixDm!.messages().then((m) => m.length), + 4, + 'alixDm should have 4 messages' + ) + + // Re-enable disappearing messages + const updatedSettings = { + disappearStartingAtNs: (await boDm.messages())[0].sentNs + 1_000_000_000, // 1s from now + retentionDurationInNs: 1_000_000_000, + } + await boDm.updateDisappearingMessageSettings(updatedSettings) + await delayToPropogate(1000) + + await boDm.sync() + await alixDm!.sync() + + await delayToPropogate(1000) + + // Validate updated settings + await assertEqual( + () => + boDm.disappearingMessageSettings().then((s) => s!.disappearStartingAtNs), + updatedSettings.disappearStartingAtNs, + 'boDm disappearStartingAtNs should match updated settings' + ) + await assertEqual( + () => + alixDm! + .disappearingMessageSettings() + .then((s) => s!.disappearStartingAtNs), + updatedSettings.disappearStartingAtNs, + 'alixDm disappearStartingAtNs should match updated settings' + ) + + // Send new messages + await boDm.send('this will disappear soon') + await alixDm!.send('so will this') + await boDm.sync() + + await assertEqual( + () => boDm.messages().then((m) => m.length), + 9, + 'boDm should have 9 messages' + ) + await assertEqual( + () => alixDm!.messages().then((m) => m.length), + 8, + 'alixDm should have 8 messages' + ) + + await delayToPropogate(6000) + + // Validate messages were deleted + await assertEqual( + () => boDm.messages().then((m) => m.length), + 7, + 'boDm should have 7 messages left' + ) + await assertEqual( + () => alixDm!.messages().then((m) => m.length), + 6, + 'alixDm should have 6 messages left' + ) + + // Final validation that settings persist + await assertEqual( + () => + boDm.disappearingMessageSettings().then((s) => s!.retentionDurationInNs), + updatedSettings.retentionDurationInNs, + 'boDm retentionDuration should match updated settings' + ) + await assertEqual( + () => + alixDm! + .disappearingMessageSettings() + .then((s) => s!.retentionDurationInNs), + updatedSettings.retentionDurationInNs, + 'alixDm retentionDuration should match updated settings' + ) + await assertEqual( + () => boDm.isDisappearingMessagesEnabled(), + true, + 'boDm should have disappearing enabled' + ) + await assertEqual( + () => alixDm!.isDisappearingMessagesEnabled(), + true, + 'alixDm should have disappearing enabled' + ) + + return true +}) diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index f331506d..a6692bdf 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -27,1994 +27,1994 @@ function test(name: string, perform: () => Promise) { groupTests.push({ name: String(counter++) + '. ' + name, run: perform }) } -// test('verify exportNativeLogs', async () => { -// await createClients(2) -// const logs = await Client.exportNativeLogs() -// assert( -// logs.includes('Created XMTP client for inbox_id'), -// 'Logs should contain Initialized identity inbox_id=' -// ) -// return true -// }) +test('verify exportNativeLogs', async () => { + await createClients(2) + const logs = await Client.exportNativeLogs() + assert( + logs.includes('Created XMTP client for inbox_id'), + 'Logs should contain Initialized identity inbox_id=' + ) + return true +}) -// test('can create a group with inbox ids default permissions', async () => { -// const [alix, bo, caro] = await createClients(3) - -// // Create group with inbox ID -// const boGroup = await bo.conversations.newGroupWithInboxIds([alix.inboxId]) - -// await alix.conversations.sync() -// await boGroup.sync() - -// const alixGroups = await alix.conversations.listGroups() -// const alixGroup = alixGroups[0] - -// // Verify group IDs are not empty -// assert(boGroup.id !== '', 'bo group ID should not be empty') -// assert(alixGroup.id !== '', 'alix group ID should not be empty') - -// // Add caro to group -// await alixGroup.addMembers([caro.address]) -// await boGroup.sync() - -// // Verify member counts -// assert( -// (await alixGroup.members()).length === 3, -// 'alix group should have 3 members' -// ) -// assert( -// (await boGroup.members()).length === 3, -// 'bo group should have 3 members' -// ) - -// // Verify remove members throws error (admin only) -// try { -// await alixGroup.removeMembers([caro.address]) -// await boGroup.sync() -// throw new Error('Should not be able to remove members') -// } catch { -// // Expected error -// } +test('can create a group with inbox ids default permissions', async () => { + const [alix, bo, caro] = await createClients(3) -// // Verify member counts unchanged -// assert( -// (await alixGroup.members()).length === 3, -// 'alix group should still have 3 members' -// ) -// assert( -// (await boGroup.members()).length === 3, -// 'bo group should still have 3 members' -// ) - -// // Check permissions -// const boPermissions = await boGroup.permissionPolicySet() -// const alixPermissions = await alixGroup.permissionPolicySet() - -// assert( -// boPermissions.addMemberPolicy === 'allow', -// 'bo group should have allow add member policy' -// ) -// assert( -// alixPermissions.addMemberPolicy === 'allow', -// 'alix group should have allow add member policy' -// ) - -// // Check super admin status -// assert( -// await boGroup.isSuperAdmin(bo.inboxId), -// 'bo should be super admin in bo group' -// ) -// assert( -// !(await boGroup.isSuperAdmin(alix.inboxId)), -// 'alix should not be super admin in bo group' -// ) -// assert( -// await alixGroup.isSuperAdmin(bo.inboxId), -// 'bo should be super admin in alix group' -// ) -// assert( -// !(await alixGroup.isSuperAdmin(alix.inboxId)), -// 'alix should not be super admin in alix group' -// ) + // Create group with inbox ID + const boGroup = await bo.conversations.newGroupWithInboxIds([alix.inboxId]) -// return true -// }) + await alix.conversations.sync() + await boGroup.sync() -// test('groups cannot fork', async () => { -// const [alix, bo, caro] = await createClients(3) -// // Create group with 3 users -// const { id: groupId } = await alix.conversations.newGroup([ -// bo.address, -// caro.address, -// ]) - -// const getGroupForClient = async (client: Client) => { -// // Always sync the client before getting the group -// await client.conversations.sync() -// const group = await client.conversations.findGroup(groupId) -// assert(group !== undefined, `Group not found for ${client.address}`) -// return group as Group -// } + const alixGroups = await alix.conversations.listGroups() + const alixGroup = alixGroups[0] -// const syncClientAndGroup = async (client: Client) => { -// const group = await getGroupForClient(client) -// await group.sync() -// } + // Verify group IDs are not empty + assert(boGroup.id !== '', 'bo group ID should not be empty') + assert(alixGroup.id !== '', 'alix group ID should not be empty') -// const addMemberToGroup = async (fromClient: Client, addresses: string[]) => { -// await syncClientAndGroup(fromClient) -// const group = await getGroupForClient(fromClient) -// await group.addMembers(addresses) -// await delayToPropogate(500) -// } + // Add caro to group + await alixGroup.addMembers([caro.address]) + await boGroup.sync() -// const removeMemberFromGroup = async ( -// fromClient: Client, -// addresses: string[] -// ) => { -// await syncClientAndGroup(fromClient) -// const group = await getGroupForClient(fromClient) -// await group.removeMembers(addresses) -// await delayToPropogate(500) -// } + // Verify member counts + assert( + (await alixGroup.members()).length === 3, + 'alix group should have 3 members' + ) + assert( + (await boGroup.members()).length === 3, + 'bo group should have 3 members' + ) -// // Helper to send a message from a bunch of senders and make sure it is received by all receivers -// const testMessageSending = async (senderClient: Client, receiver: Client) => { -// // for (const senderClient of senders) { -// const messageContent = Math.random().toString(36) -// await syncClientAndGroup(senderClient) -// const senderGroup = await getGroupForClient(senderClient) -// await senderGroup.send(messageContent) - -// await delayToPropogate(500) -// await senderGroup.sync() - -// await syncClientAndGroup(receiver) - -// const receiverGroupToCheck = await getGroupForClient(receiver) -// await receiverGroupToCheck.sync() - -// const messages = await receiverGroupToCheck.messages({ -// direction: 'DESCENDING', -// }) -// const lastMessage = messages[0] -// // console.log(lastMessage); -// console.log( -// `${receiverGroupToCheck.client.installationId} sees ${messages.length} messages in group` -// ) -// assert( -// lastMessage !== undefined && -// lastMessage.nativeContent.text === messageContent, -// `${receiverGroupToCheck.client.installationId} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` -// ) -// // } -// } + // Verify remove members throws error (admin only) + try { + await alixGroup.removeMembers([caro.address]) + await boGroup.sync() + throw new Error('Should not be able to remove members') + } catch { + // Expected error + } -// console.log('Testing that messages sent by alix are received by bo') -// await testMessageSending(alix, bo) -// console.log('Alix & Bo are not forked at the beginning') + // Verify member counts unchanged + assert( + (await alixGroup.members()).length === 3, + 'alix group should still have 3 members' + ) + assert( + (await boGroup.members()).length === 3, + 'bo group should still have 3 members' + ) -// // Test adding members one by one -// // console.log('Testing adding members one by one...') -// const newClients = await createClients(2) + // Check permissions + const boPermissions = await boGroup.permissionPolicySet() + const alixPermissions = await alixGroup.permissionPolicySet() -// // Add back several members -// console.log('Adding new members to the group...') -// for (const client of newClients) { -// console.log(`Adding member ${client.address}...`) -// await addMemberToGroup(alix, [client.address]) -// } -// await delayToPropogate() + assert( + boPermissions.addMemberPolicy === 'allow', + 'bo group should have allow add member policy' + ) + assert( + alixPermissions.addMemberPolicy === 'allow', + 'alix group should have allow add member policy' + ) -// await alix.conversations.sync() -// await syncClientAndGroup(alix) - -// // NB => if we don't use Promise.all but a loop, we don't get a fork -// const REMOVE_MEMBERS_IN_PARALLEL = true -// if (REMOVE_MEMBERS_IN_PARALLEL) { -// console.log('Removing members in parallel') - -// await Promise.all( -// newClients.map((client) => { -// console.log(`Removing member ${client.address}...`) -// return removeMemberFromGroup(alix, [client.address]) -// }) -// ) -// } else { -// console.log('Removing members one by one') - -// for (const client of newClients) { -// console.log(`Removing member ${client.address}...`) -// await removeMemberFromGroup(alix, [client.address]) -// } -// } + // Check super admin status + assert( + await boGroup.isSuperAdmin(bo.inboxId), + 'bo should be super admin in bo group' + ) + assert( + !(await boGroup.isSuperAdmin(alix.inboxId)), + 'alix should not be super admin in bo group' + ) + assert( + await alixGroup.isSuperAdmin(bo.inboxId), + 'bo should be super admin in alix group' + ) + assert( + !(await alixGroup.isSuperAdmin(alix.inboxId)), + 'alix should not be super admin in alix group' + ) -// await delayToPropogate(1000) - -// // When forked, it stays forked even if we try 5 times -// // but sometimes it is not forked and works 5/5 times -// let forkCount = 0 -// const tryCount = 5 -// for (let i = 0; i < tryCount; i++) { -// console.log(`Checking fork status ${i + 1}/${tryCount}`) -// try { -// await syncClientAndGroup(alix) -// await syncClientAndGroup(bo) -// await delayToPropogate(500) -// await testMessageSending(alix, bo) -// console.log('Not forked!') -// } catch (e: any) { -// console.log('Forked!') -// console.log(e) -// forkCount++ -// } -// } + return true +}) -// assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) +test('groups cannot fork', async () => { + const [alix, bo, caro] = await createClients(3) + // Create group with 3 users + const { id: groupId } = await alix.conversations.newGroup([ + bo.address, + caro.address, + ]) + + const getGroupForClient = async (client: Client) => { + // Always sync the client before getting the group + await client.conversations.sync() + const group = await client.conversations.findGroup(groupId) + assert(group !== undefined, `Group not found for ${client.address}`) + return group as Group + } -// return true -// }) + const syncClientAndGroup = async (client: Client) => { + const group = await getGroupForClient(client) + await group.sync() + } -// test('groups cannot fork short version', async () => { -// const [alix, bo, new_one, new_two] = await createClients(4) -// // Create group with 2 users -// const alixGroup = await alix.conversations.newGroup([ -// bo.address, -// new_one.address, -// new_two.address, -// ]) - -// // sync clients -// await alix.conversations.sync() -// await bo.conversations.sync() -// const boGroup: Group = (await bo.conversations.findGroup( -// alixGroup.id -// ))! - -// // Remove two members in parallel -// // NB => if we don't use Promise.all but a loop, we don't get a fork -// console.log( -// '*************libxmtp*********************: Removing members in parallel' -// ) -// await Promise.all([ -// alixGroup.removeMembers([new_one.address]), -// alixGroup.removeMembers([new_two.address]), -// ]) - -// // Helper to send a message from a bunch of senders and make sure it is received by all receivers -// const testMessageSending = async ( -// senderGroup: Group, -// receiverGroup: Group -// ) => { -// const messageContent = Math.random().toString(36) -// await senderGroup.sync() -// await alixGroup.send(messageContent) - -// await delayToPropogate(500) -// await alixGroup.sync() -// await receiverGroup.sync() - -// const messages = await receiverGroup.messages({ -// direction: 'DESCENDING', -// }) -// const lastMessage = messages[0] -// console.log( -// `${receiverGroup.client.installationId} sees ${messages.length} messages in group` -// ) -// assert( -// lastMessage !== undefined && -// lastMessage.nativeContent.text === messageContent, -// `${receiverGroup.client.installationId} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` -// ) -// } -// // When forked, it stays forked even if we try 5 times -// // but sometimes it is not forked and works 5/5 times -// let forkCount = 0 -// const tryCount = 5 -// for (let i = 0; i < tryCount; i++) { -// console.log(`Checking fork status ${i + 1}/${tryCount}`) -// try { -// await alixGroup.sync() -// await boGroup.sync() -// await delayToPropogate(500) -// await testMessageSending(alixGroup, boGroup) -// console.log('Not forked!') -// } catch (e: any) { -// console.log('Forked!') -// console.log(e) -// forkCount++ -// } -// } -// assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) + const addMemberToGroup = async (fromClient: Client, addresses: string[]) => { + await syncClientAndGroup(fromClient) + const group = await getGroupForClient(fromClient) + await group.addMembers(addresses) + await delayToPropogate(500) + } -// return true -// }) + const removeMemberFromGroup = async ( + fromClient: Client, + addresses: string[] + ) => { + await syncClientAndGroup(fromClient) + const group = await getGroupForClient(fromClient) + await group.removeMembers(addresses) + await delayToPropogate(500) + } -// test('groups cannot fork short version - update metadata', async () => { -// const [alix, bo, new_one, new_two] = await createClients(6) -// // Create group with 2 users -// const alixGroup = await alix.conversations.newGroup([ -// bo.address, -// new_one.address, -// new_two.address, -// ]) - -// // sync clients -// await alix.conversations.sync() -// await bo.conversations.sync() -// const boGroup: Group = (await bo.conversations.findGroup( -// alixGroup.id -// ))! - -// // Remove two members in parallel -// // NB => if we don't use Promise.all but a loop, we don't get a fork -// console.log( -// '*************libxmtp*********************: Updating metadata in parallel' -// ) -// await Promise.all([ -// alixGroup.updateGroupName('new name'), -// alixGroup.updateGroupName('new name 2'), -// ]) - -// // Helper to send a message from a bunch of senders and make sure it is received by all receivers -// const testMessageSending = async ( -// senderGroup: Group, -// receiverGroup: Group -// ) => { -// const messageContent = Math.random().toString(36) -// await senderGroup.sync() -// await alixGroup.send(messageContent) - -// await delayToPropogate(500) -// await alixGroup.sync() -// await receiverGroup.sync() - -// const messages = await receiverGroup.messages({ -// direction: 'DESCENDING', -// }) -// const lastMessage = messages[0] -// console.log( -// `${receiverGroup.client.installationId} sees ${messages.length} messages in group` -// ) -// assert( -// lastMessage !== undefined && -// lastMessage.nativeContent.text === messageContent, -// `${receiverGroup.client.installationId} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` -// ) -// } -// // When forked, it stays forked even if we try 5 times -// // but sometimes it is not forked and works 5/5 times -// let forkCount = 0 -// const tryCount = 5 -// for (let i = 0; i < tryCount; i++) { -// console.log(`Checking fork status ${i + 1}/${tryCount}`) -// try { -// await alixGroup.sync() -// await boGroup.sync() -// await delayToPropogate(500) -// await testMessageSending(alixGroup, boGroup) -// console.log('Not forked!') -// } catch (e: any) { -// console.log('Forked!') -// console.log(e) -// forkCount++ -// } -// } -// assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) + // Helper to send a message from a bunch of senders and make sure it is received by all receivers + const testMessageSending = async (senderClient: Client, receiver: Client) => { + // for (const senderClient of senders) { + const messageContent = Math.random().toString(36) + await syncClientAndGroup(senderClient) + const senderGroup = await getGroupForClient(senderClient) + await senderGroup.send(messageContent) + + await delayToPropogate(500) + await senderGroup.sync() + + await syncClientAndGroup(receiver) + + const receiverGroupToCheck = await getGroupForClient(receiver) + await receiverGroupToCheck.sync() + + const messages = await receiverGroupToCheck.messages({ + direction: 'DESCENDING', + }) + const lastMessage = messages[0] + // console.log(lastMessage); + console.log( + `${receiverGroupToCheck.client.installationId} sees ${messages.length} messages in group` + ) + assert( + lastMessage !== undefined && + lastMessage.nativeContent.text === messageContent, + `${receiverGroupToCheck.client.installationId} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` + ) + // } + } -// return true -// }) + console.log('Testing that messages sent by alix are received by bo') + await testMessageSending(alix, bo) + console.log('Alix & Bo are not forked at the beginning') -// test('can cancel streams', async () => { -// const [alix, bo] = await createClients(2) -// let messageCallbacks = 0 + // Test adding members one by one + // console.log('Testing adding members one by one...') + const newClients = await createClients(2) -// await bo.conversations.streamAllMessages(async () => { -// messageCallbacks++ -// }) + // Add back several members + console.log('Adding new members to the group...') + for (const client of newClients) { + console.log(`Adding member ${client.address}...`) + await addMemberToGroup(alix, [client.address]) + } + await delayToPropogate() + + await alix.conversations.sync() + await syncClientAndGroup(alix) + + // NB => if we don't use Promise.all but a loop, we don't get a fork + const REMOVE_MEMBERS_IN_PARALLEL = true + if (REMOVE_MEMBERS_IN_PARALLEL) { + console.log('Removing members in parallel') + + await Promise.all( + newClients.map((client) => { + console.log(`Removing member ${client.address}...`) + return removeMemberFromGroup(alix, [client.address]) + }) + ) + } else { + console.log('Removing members one by one') + + for (const client of newClients) { + console.log(`Removing member ${client.address}...`) + await removeMemberFromGroup(alix, [client.address]) + } + } -// const group = await alix.conversations.newGroup([bo.address]) -// await group.send('hello') -// await delayToPropogate() + await delayToPropogate(1000) -// assert( -// messageCallbacks === 1, -// 'message stream should have received 1 message' -// ) + // When forked, it stays forked even if we try 5 times + // but sometimes it is not forked and works 5/5 times + let forkCount = 0 + const tryCount = 5 + for (let i = 0; i < tryCount; i++) { + console.log(`Checking fork status ${i + 1}/${tryCount}`) + try { + await syncClientAndGroup(alix) + await syncClientAndGroup(bo) + await delayToPropogate(500) + await testMessageSending(alix, bo) + console.log('Not forked!') + } catch (e: any) { + console.log('Forked!') + console.log(e) + forkCount++ + } + } -// await bo.conversations.cancelStreamAllMessages() -// await delayToPropogate() + assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) -// await group.send('hello') -// await group.send('hello') -// await group.send('hello') + return true +}) -// await delayToPropogate() +test('groups cannot fork short version', async () => { + const [alix, bo, new_one, new_two] = await createClients(4) + // Create group with 2 users + const alixGroup = await alix.conversations.newGroup([ + bo.address, + new_one.address, + new_two.address, + ]) + + // sync clients + await alix.conversations.sync() + await bo.conversations.sync() + const boGroup: Group = (await bo.conversations.findGroup( + alixGroup.id + ))! + + // Remove two members in parallel + // NB => if we don't use Promise.all but a loop, we don't get a fork + console.log( + '*************libxmtp*********************: Removing members in parallel' + ) + await Promise.all([ + alixGroup.removeMembers([new_one.address]), + alixGroup.removeMembers([new_two.address]), + ]) + + // Helper to send a message from a bunch of senders and make sure it is received by all receivers + const testMessageSending = async ( + senderGroup: Group, + receiverGroup: Group + ) => { + const messageContent = Math.random().toString(36) + await senderGroup.sync() + await alixGroup.send(messageContent) + + await delayToPropogate(500) + await alixGroup.sync() + await receiverGroup.sync() + + const messages = await receiverGroup.messages({ + direction: 'DESCENDING', + }) + const lastMessage = messages[0] + console.log( + `${receiverGroup.client.installationId} sees ${messages.length} messages in group` + ) + assert( + lastMessage !== undefined && + lastMessage.nativeContent.text === messageContent, + `${receiverGroup.client.installationId} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` + ) + } + // When forked, it stays forked even if we try 5 times + // but sometimes it is not forked and works 5/5 times + let forkCount = 0 + const tryCount = 5 + for (let i = 0; i < tryCount; i++) { + console.log(`Checking fork status ${i + 1}/${tryCount}`) + try { + await alixGroup.sync() + await boGroup.sync() + await delayToPropogate(500) + await testMessageSending(alixGroup, boGroup) + console.log('Not forked!') + } catch (e: any) { + console.log('Forked!') + console.log(e) + forkCount++ + } + } + assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) -// assert( -// messageCallbacks === 1, -// 'message stream should still only received 1 message' -// ) + return true +}) -// await bo.conversations.streamAllMessages(async () => { -// messageCallbacks++ -// }) +test('groups cannot fork short version - update metadata', async () => { + const [alix, bo, new_one, new_two] = await createClients(6) + // Create group with 2 users + const alixGroup = await alix.conversations.newGroup([ + bo.address, + new_one.address, + new_two.address, + ]) + + // sync clients + await alix.conversations.sync() + await bo.conversations.sync() + const boGroup: Group = (await bo.conversations.findGroup( + alixGroup.id + ))! + + // Remove two members in parallel + // NB => if we don't use Promise.all but a loop, we don't get a fork + console.log( + '*************libxmtp*********************: Updating metadata in parallel' + ) + await Promise.all([ + alixGroup.updateGroupName('new name'), + alixGroup.updateGroupName('new name 2'), + ]) + + // Helper to send a message from a bunch of senders and make sure it is received by all receivers + const testMessageSending = async ( + senderGroup: Group, + receiverGroup: Group + ) => { + const messageContent = Math.random().toString(36) + await senderGroup.sync() + await alixGroup.send(messageContent) + + await delayToPropogate(500) + await alixGroup.sync() + await receiverGroup.sync() + + const messages = await receiverGroup.messages({ + direction: 'DESCENDING', + }) + const lastMessage = messages[0] + console.log( + `${receiverGroup.client.installationId} sees ${messages.length} messages in group` + ) + assert( + lastMessage !== undefined && + lastMessage.nativeContent.text === messageContent, + `${receiverGroup.client.installationId} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` + ) + } + // When forked, it stays forked even if we try 5 times + // but sometimes it is not forked and works 5/5 times + let forkCount = 0 + const tryCount = 5 + for (let i = 0; i < tryCount; i++) { + console.log(`Checking fork status ${i + 1}/${tryCount}`) + try { + await alixGroup.sync() + await boGroup.sync() + await delayToPropogate(500) + await testMessageSending(alixGroup, boGroup) + console.log('Not forked!') + } catch (e: any) { + console.log('Forked!') + console.log(e) + forkCount++ + } + } + assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) -// await delayToPropogate() + return true +}) -// await group.send('hello') -// await delayToPropogate() +test('can cancel streams', async () => { + const [alix, bo] = await createClients(2) + let messageCallbacks = 0 -// assert( -// messageCallbacks === 2, -// 'message stream should have received 2 message' -// ) + await bo.conversations.streamAllMessages(async () => { + messageCallbacks++ + }) -// return true -// }) + const group = await alix.conversations.newGroup([bo.address]) + await group.send('hello') + await delayToPropogate() -// test('group message delivery status', async () => { -// const [alixClient, boClient] = await createClients(2) -// const alixGroup = await alixClient.conversations.newGroup([boClient.address]) + assert( + messageCallbacks === 1, + 'message stream should have received 1 message' + ) -// await alixGroup.send('hello, world') + await bo.conversations.cancelStreamAllMessages() + await delayToPropogate() -// const alixMessages: DecodedMessage[] = await alixGroup.messages() + await group.send('hello') + await group.send('hello') + await group.send('hello') -// assert( -// alixMessages.length === 2, -// `the messages length should be 2 but was ${alixMessages.length}` -// ) + await delayToPropogate() -// await alixGroup.sync() -// const alixMessages2: DecodedMessage[] = await alixGroup.messages() + assert( + messageCallbacks === 1, + 'message stream should still only received 1 message' + ) -// assert( -// alixMessages2.length === 2, -// `the messages length should be 2 but was ${alixMessages.length}` -// ) + await bo.conversations.streamAllMessages(async () => { + messageCallbacks++ + }) -// assert( -// alixMessages2[0].deliveryStatus === 'PUBLISHED', -// `the message should have a delivery status of PUBLISHED but was ${alixMessages2[0].deliveryStatus}` -// ) + await delayToPropogate() -// await boClient.conversations.sync() -// const boGroup = (await boClient.conversations.listGroups())[0] -// await boGroup.sync() -// const boMessages: DecodedMessage[] = await boGroup.messages() + await group.send('hello') + await delayToPropogate() -// assert( -// boMessages.length === 1, -// `the messages length should be 1 but was ${boMessages.length}` -// ) + assert( + messageCallbacks === 2, + 'message stream should have received 2 message' + ) -// assert( -// boMessages[0].deliveryStatus === 'PUBLISHED', -// `the message should have a delivery status of PUBLISHED but was ${boMessages[0].deliveryStatus}` -// ) + return true +}) -// return true -// }) +test('group message delivery status', async () => { + const [alixClient, boClient] = await createClients(2) + const alixGroup = await alixClient.conversations.newGroup([boClient.address]) -// test('can find a group by id', async () => { -// const [alixClient, boClient] = await createClients(2) -// const alixGroup = await alixClient.conversations.newGroup([boClient.address]) + await alixGroup.send('hello, world') -// await boClient.conversations.sync() -// const boGroup = await boClient.conversations.findGroup(alixGroup.id) + const alixMessages: DecodedMessage[] = await alixGroup.messages() -// assert( -// boGroup?.id === alixGroup.id, -// `bo ${boGroup?.id} does not match alix ${alixGroup.id}` -// ) -// return true -// }) + assert( + alixMessages.length === 2, + `the messages length should be 2 but was ${alixMessages.length}` + ) -// 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 alixGroup.sync() + const alixMessages2: DecodedMessage[] = await alixGroup.messages() -// await boClient.conversations.sync() -// const boGroup = await boClient.conversations.findGroup(alixGroup.id) -// await boGroup?.sync() -// const boMessage = await boClient.conversations.findMessage(alixMessageId) + assert( + alixMessages2.length === 2, + `the messages length should be 2 but was ${alixMessages.length}` + ) -// assert( -// boMessage?.id === alixMessageId, -// `bo message ${boMessage?.id} does not match ${alixMessageId}` -// ) -// return true -// }) + assert( + alixMessages2[0].deliveryStatus === 'PUBLISHED', + `the message should have a delivery status of PUBLISHED but was ${alixMessages2[0].deliveryStatus}` + ) -// test('who added me to a group', async () => { -// const [alixClient, boClient] = await createClients(2) -// await alixClient.conversations.newGroup([boClient.address]) + await boClient.conversations.sync() + const boGroup = (await boClient.conversations.listGroups())[0] + await boGroup.sync() + const boMessages: DecodedMessage[] = await boGroup.messages() -// await boClient.conversations.sync() -// const boGroup = (await boClient.conversations.listGroups())[0] -// const addedByInboxId = await boGroup.addedByInboxId + assert( + boMessages.length === 1, + `the messages length should be 1 but was ${boMessages.length}` + ) -// assert( -// addedByInboxId === alixClient.inboxId, -// `addedByInboxId ${addedByInboxId} does not match ${alixClient.inboxId}` -// ) -// return true -// }) + assert( + boMessages[0].deliveryStatus === 'PUBLISHED', + `the message should have a delivery status of PUBLISHED but was ${boMessages[0].deliveryStatus}` + ) -// test('can get members of a group', async () => { -// const [alixClient, boClient] = await createClients(2) -// const group = await alixClient.conversations.newGroup([boClient.address]) - -// const members = await group.members() - -// assert(members.length === 2, `Should be 2 members but was ${members.length}`) - -// // We can not be sure of the order that members will be returned in -// for (const member of members) { -// // Alix created the group so they are a super admin -// if ( -// member.addresses[0].toLocaleLowerCase() === -// alixClient.address.toLocaleLowerCase() -// ) { -// assert( -// member.permissionLevel === 'super_admin', -// `Should be super_admin but was ${member.permissionLevel}` -// ) -// } -// // Bo did not create the group so he defaults to permission level "member" -// if ( -// member.addresses[0].toLocaleLowerCase() === -// boClient.address.toLocaleLowerCase() -// ) { -// assert( -// member.permissionLevel === 'member', -// `Should be member but was ${member.permissionLevel}` -// ) -// } -// } -// return true -// }) + return true +}) -// test('can message in a group', async () => { -// const [alixClient, boClient, caroClient] = await createClients(3) +test('can find a group by id', async () => { + const [alixClient, boClient] = await createClients(2) + const alixGroup = await alixClient.conversations.newGroup([boClient.address]) -// // alix's num groups start at 0 -// let alixGroups = await alixClient.conversations.listGroups() -// if (alixGroups.length !== 0) { -// throw new Error('num groups should be 0') -// } + await boClient.conversations.sync() + const boGroup = await boClient.conversations.findGroup(alixGroup.id) -// // alix creates a group -// const alixGroup = await alixClient.conversations.newGroup([ -// boClient.address, -// caroClient.address, -// ]) - -// // alix's num groups == 1 -// await alixClient.conversations.sync() -// alixGroups = await alixClient.conversations.listGroups() -// if (alixGroups.length !== 1) { -// throw new Error('num groups should be 1') -// } + assert( + boGroup?.id === alixGroup.id, + `bo ${boGroup?.id} does not match alix ${alixGroup.id}` + ) + return true +}) -// // alix group should match create time from list function -// assert(alixGroups[0].createdAt === alixGroup.createdAt, 'group create time') +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') -// // alix can confirm memberInboxIds -// await alixGroup.sync() -// const memberInboxIds = await alixGroup.memberInboxIds() -// if (memberInboxIds.length !== 3) { -// throw new Error('num group members should be 3') -// } + await boClient.conversations.sync() + const boGroup = await boClient.conversations.findGroup(alixGroup.id) + await boGroup?.sync() + const boMessage = await boClient.conversations.findMessage(alixMessageId) -// if ( -// !( -// memberInboxIds.includes(alixClient.inboxId) && -// memberInboxIds.includes(boClient.inboxId) && -// memberInboxIds.includes(caroClient.inboxId) -// ) -// ) { -// throw new Error('missing address') -// } + assert( + boMessage?.id === alixMessageId, + `bo message ${boMessage?.id} does not match ${alixMessageId}` + ) + return true +}) -// // alix can send messages -// await alixGroup.send('hello, world') -// await alixGroup.send('gm') - -// // bo's num groups == 1 -// await boClient.conversations.sync() -// const boGroups = await boClient.conversations.listGroups() -// if (boGroups.length !== 1) { -// throw new Error( -// 'num groups for bo should be 1, but it is' + boGroups.length -// ) -// } -// await delayToPropogate() -// // bo can read messages from alix -// await boGroups[0].sync() -// const boMessages: DecodedMessage[] = await boGroups[0].messages() - -// if (boMessages.length !== 2) { -// throw new Error( -// 'num messages for bo should be 2, but it is' + boMessages.length -// ) -// } -// if (boMessages[0].content() !== 'gm') { -// throw new Error("newest message should be 'gm'") -// } -// if (boMessages[1].content() !== 'hello, world') { -// throw new Error("newest message should be 'hello, world'") -// } -// // bo can send a message -// await boGroups[0].send('hey guys!') - -// // caro's num groups == 1 -// await caroClient.conversations.sync() -// const caroGroups = await caroClient.conversations.listGroups() -// if (caroGroups.length !== 1) { -// throw new Error( -// 'num groups for caro should be 1, but it is' + caroGroups.length -// ) -// } +test('who added me to a group', async () => { + const [alixClient, boClient] = await createClients(2) + await alixClient.conversations.newGroup([boClient.address]) -// // caro can read messages from alix and bo -// await caroGroups[0].sync() -// const caroMessages = await caroGroups[0].messages() + await boClient.conversations.sync() + const boGroup = (await boClient.conversations.listGroups())[0] + const addedByInboxId = await boGroup.addedByInboxId -// if (caroMessages.length !== 3) { -// throw new Error(`length should be 3 but was ${caroMessages.length}`) -// } -// if (caroMessages[0].content() !== 'hey guys!') { -// throw new Error( -// `newest Message should be 'hey guys!' but was ${caroMessages[0].content()}` -// ) -// } -// if (caroMessages[1].content() !== 'gm') { -// throw new Error( -// `second Message should be 'gm' but was ${caroMessages[1].content()}` -// ) -// } + assert( + addedByInboxId === alixClient.inboxId, + `addedByInboxId ${addedByInboxId} does not match ${alixClient.inboxId}` + ) + return true +}) -// return true -// }) +test('can get members of a group', async () => { + const [alixClient, boClient] = await createClients(2) + const group = await alixClient.conversations.newGroup([boClient.address]) + + const members = await group.members() + + assert(members.length === 2, `Should be 2 members but was ${members.length}`) + + // We can not be sure of the order that members will be returned in + for (const member of members) { + // Alix created the group so they are a super admin + if ( + member.addresses[0].toLocaleLowerCase() === + alixClient.address.toLocaleLowerCase() + ) { + assert( + member.permissionLevel === 'super_admin', + `Should be super_admin but was ${member.permissionLevel}` + ) + } + // Bo did not create the group so he defaults to permission level "member" + if ( + member.addresses[0].toLocaleLowerCase() === + boClient.address.toLocaleLowerCase() + ) { + assert( + member.permissionLevel === 'member', + `Should be member but was ${member.permissionLevel}` + ) + } + } + 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]) -// assert( -// (await boGroup.consentState()) === 'allowed', -// 'consent should be allowed' -// ) - -// // Sync Alice's client to get the new group -// await alixClient.conversations.sync() -// const alixGroup = await alixClient.conversations.findGroup(boGroup.id) -// if (!alixGroup) { -// throw new Error(`Group not found for id: ${boGroup.id}`) -// } +test('can message in a group', async () => { + const [alixClient, boClient, caroClient] = await createClients(3) -// // Check if the group is allowed initially -// const alixGroupState = await alixClient.preferences.conversationConsentState( -// boGroup.id -// ) -// if (alixGroupState !== 'unknown') { -// throw new Error('Group should not be allowed initially') -// } + // alix's num groups start at 0 + let alixGroups = await alixClient.conversations.listGroups() + if (alixGroups.length !== 0) { + throw new Error('num groups should be 0') + } -// // Prepare a message in the group -// const preparedMessageId = await alixGroup.prepareMessage('Test text') + // alix creates a group + const alixGroup = await alixClient.conversations.newGroup([ + boClient.address, + caroClient.address, + ]) + + // alix's num groups == 1 + await alixClient.conversations.sync() + alixGroups = await alixClient.conversations.listGroups() + if (alixGroups.length !== 1) { + throw new Error('num groups should be 1') + } -// // 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}`) -// } + // alix group should match create time from list function + assert(alixGroups[0].createdAt === alixGroup.createdAt, 'group create time') -// // Publish the prepared message -// await alixGroup.publishPreparedMessages() + // alix can confirm memberInboxIds + await alixGroup.sync() + const memberInboxIds = await alixGroup.memberInboxIds() + if (memberInboxIds.length !== 3) { + throw new Error('num group members should be 3') + } -// // Sync the group after publishing the message -// await alixGroup.sync() -// messageCount = (await alixGroup.messages()).length -// if (messageCount !== 1) { -// throw new Error(`Message count should be 1, but it is ${messageCount}`) -// } + if ( + !( + memberInboxIds.includes(alixClient.inboxId) && + memberInboxIds.includes(boClient.inboxId) && + memberInboxIds.includes(caroClient.inboxId) + ) + ) { + throw new Error('missing address') + } -// // Check if the group is allowed after preparing the message -// const isGroupAllowed = await alixClient.preferences.conversationConsentState( -// boGroup.id -// ) -// if (isGroupAllowed !== 'allowed') { -// throw new Error('Group should be allowed after preparing a message') -// } + // alix can send messages + await alixGroup.send('hello, world') + await alixGroup.send('gm') + + // bo's num groups == 1 + await boClient.conversations.sync() + const boGroups = await boClient.conversations.listGroups() + if (boGroups.length !== 1) { + throw new Error( + 'num groups for bo should be 1, but it is' + boGroups.length + ) + } + await delayToPropogate() + // bo can read messages from alix + await boGroups[0].sync() + const boMessages: DecodedMessage[] = await boGroups[0].messages() + + if (boMessages.length !== 2) { + throw new Error( + 'num messages for bo should be 2, but it is' + boMessages.length + ) + } + if (boMessages[0].content() !== 'gm') { + throw new Error("newest message should be 'gm'") + } + if (boMessages[1].content() !== 'hello, world') { + throw new Error("newest message should be 'hello, world'") + } + // bo can send a message + await boGroups[0].send('hey guys!') + + // caro's num groups == 1 + await caroClient.conversations.sync() + const caroGroups = await caroClient.conversations.listGroups() + if (caroGroups.length !== 1) { + throw new Error( + 'num groups for caro should be 1, but it is' + caroGroups.length + ) + } -// // 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`) -// } + // caro can read messages from alix and bo + await caroGroups[0].sync() + const caroMessages = await caroGroups[0].messages() -// return true -// }) + if (caroMessages.length !== 3) { + throw new Error(`length should be 3 but was ${caroMessages.length}`) + } + if (caroMessages[0].content() !== 'hey guys!') { + throw new Error( + `newest Message should be 'hey guys!' but was ${caroMessages[0].content()}` + ) + } + if (caroMessages[1].content() !== 'gm') { + throw new Error( + `second Message should be 'gm' but was ${caroMessages[1].content()}` + ) + } -// test('can add members to a group', async () => { -// // Create three MLS enabled Clients -// const [alixClient, boClient, caroClient] = await createClients(3) + return true +}) -// // alix's num groups start at 0 -// let alixGroups = await alixClient.conversations.listGroups() -// if (alixGroups.length !== 0) { -// throw new Error('num groups should be 0') -// } +test('unpublished messages handling', async () => { + // Initialize fixture clients + const [alixClient, boClient] = await createClients(3) -// // bo's num groups start at 0 -// let boGroups = await boClient.conversations.listGroups() -// if (boGroups.length !== 0) { -// throw new Error('num groups should be 0') -// } + // Create a new group with Bob and Alice + const boGroup = await boClient.conversations.newGroup([alixClient.address]) + assert( + (await boGroup.consentState()) === 'allowed', + 'consent should be allowed' + ) -// // caro's num groups start at 0 -// let caroGroups = await caroClient.conversations.listGroups() -// if (caroGroups.length !== 0) { -// throw new Error('num groups should be 0') -// } + // Sync Alice's client to get the new group + await alixClient.conversations.sync() + const alixGroup = await alixClient.conversations.findGroup(boGroup.id) + if (!alixGroup) { + throw new Error(`Group not found for id: ${boGroup.id}`) + } -// // alix creates a group -// const alixGroup = await alixClient.conversations.newGroup([boClient.address]) + // Check if the group is allowed initially + const alixGroupState = await alixClient.preferences.conversationConsentState( + boGroup.id + ) + if (alixGroupState !== 'unknown') { + throw new Error('Group should not be allowed initially') + } -// // alix's num groups == 1 -// await alixClient.conversations.sync() -// alixGroups = await alixClient.conversations.listGroups() -// if (alixGroups.length !== 1) { -// throw new Error('num groups should be 1') -// } + // Prepare a message in the group + const preparedMessageId = await alixGroup.prepareMessage('Test text') -// // alix can confirm memberInboxIds -// await alixGroup.sync() -// const memberInboxIds = await alixGroup.memberInboxIds() -// if (memberInboxIds.length !== 2) { -// throw new Error('num group members should be 2') -// } -// if ( -// !( -// memberInboxIds.includes(alixClient.inboxId) && -// memberInboxIds.includes(boClient.inboxId) -// ) -// ) { -// throw new Error('missing address') -// } + // 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}`) + } -// // alix can send messages -// await alixGroup.send('hello, world') -// await alixGroup.send('gm') - -// // bo's num groups == 1 -// await boClient.conversations.sync() -// boGroups = await boClient.conversations.listGroups() -// if (boGroups.length !== 1) { -// throw new Error( -// 'num groups for bo should be 1, but it is' + boGroups.length -// ) -// } + // Publish the prepared message + await alixGroup.publishPreparedMessages() -// await alixGroup.addMembers([caroClient.address]) + // Sync the group after publishing the message + await alixGroup.sync() + messageCount = (await alixGroup.messages()).length + if (messageCount !== 1) { + throw new Error(`Message count should be 1, but it is ${messageCount}`) + } -// // caro's num groups == 1 -// await caroClient.conversations.sync() -// caroGroups = await caroClient.conversations.listGroups() -// if (caroGroups.length !== 1) { -// throw new Error( -// 'num groups for caro should be 1, but it is' + caroGroups.length -// ) -// } -// await caroGroups[0].sync() -// const caroMessages = await caroGroups[0].messages() -// if (caroMessages.length !== 0) { -// throw new Error('num messages for caro should be 0') -// } + // Check if the group is allowed after preparing the message + const isGroupAllowed = await alixClient.preferences.conversationConsentState( + boGroup.id + ) + if (isGroupAllowed !== 'allowed') { + throw new Error('Group should be allowed after preparing a message') + } + + // 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`) + } + + return true +}) + +test('can add members to a group', async () => { + // Create three MLS enabled Clients + const [alixClient, boClient, caroClient] = await createClients(3) + + // alix's num groups start at 0 + let alixGroups = await alixClient.conversations.listGroups() + if (alixGroups.length !== 0) { + throw new Error('num groups should be 0') + } + + // bo's num groups start at 0 + let boGroups = await boClient.conversations.listGroups() + if (boGroups.length !== 0) { + throw new Error('num groups should be 0') + } + + // caro's num groups start at 0 + let caroGroups = await caroClient.conversations.listGroups() + if (caroGroups.length !== 0) { + throw new Error('num groups should be 0') + } + + // alix creates a group + const alixGroup = await alixClient.conversations.newGroup([boClient.address]) + + // alix's num groups == 1 + await alixClient.conversations.sync() + alixGroups = await alixClient.conversations.listGroups() + if (alixGroups.length !== 1) { + throw new Error('num groups should be 1') + } + + // alix can confirm memberInboxIds + await alixGroup.sync() + const memberInboxIds = await alixGroup.memberInboxIds() + if (memberInboxIds.length !== 2) { + throw new Error('num group members should be 2') + } + if ( + !( + memberInboxIds.includes(alixClient.inboxId) && + memberInboxIds.includes(boClient.inboxId) + ) + ) { + throw new Error('missing address') + } + + // alix can send messages + await alixGroup.send('hello, world') + await alixGroup.send('gm') + + // bo's num groups == 1 + await boClient.conversations.sync() + boGroups = await boClient.conversations.listGroups() + if (boGroups.length !== 1) { + throw new Error( + 'num groups for bo should be 1, but it is' + boGroups.length + ) + } + + await alixGroup.addMembers([caroClient.address]) + + // caro's num groups == 1 + await caroClient.conversations.sync() + caroGroups = await caroClient.conversations.listGroups() + if (caroGroups.length !== 1) { + throw new Error( + 'num groups for caro should be 1, but it is' + caroGroups.length + ) + } + await caroGroups[0].sync() + const caroMessages = await caroGroups[0].messages() + if (caroMessages.length !== 0) { + throw new Error('num messages for caro should be 0') + } + + await boGroups[0].sync() + const boGroupMembers = await boGroups[0].memberInboxIds() + if (boGroupMembers.length !== 3) { + throw new Error('num group members should be 3') + } + + return true +}) + +test('can remove members from a group', async () => { + // Create three MLS enabled Clients + const [alixClient, boClient, caroClient] = await createClients(3) + + // alix's num groups start at 0 + let alixGroups = await alixClient.conversations.listGroups() + if (alixGroups.length !== 0) { + throw new Error('num groups should be 0') + } + + // bo's num groups start at 0 + let boGroups = await boClient.conversations.listGroups() + assert(boGroups.length === 0, 'num groups should be 0') + + // caro's num groups start at 0 + let caroGroups = await caroClient.conversations.listGroups() + if (caroGroups.length !== 0) { + throw new Error('num groups should be 0') + } + + // alix creates a group + const alixGroup = await alixClient.conversations.newGroup([ + boClient.address, + caroClient.address, + ]) + + // alix's num groups == 1 + await alixClient.conversations.sync() + alixGroups = await alixClient.conversations.listGroups() + if (alixGroups.length !== 1) { + throw new Error('num groups should be 1') + } + + // alix can confirm memberInboxIds + await alixGroup.sync() + const memberInboxIds = await alixGroup.memberInboxIds() + if (memberInboxIds.length !== 3) { + throw new Error('num group members should be 3') + } + if ( + !( + memberInboxIds.includes(alixClient.inboxId) && + memberInboxIds.includes(boClient.inboxId) + ) + ) { + throw new Error('missing address') + } + + // alix can send messages + await alixGroup.send('hello, world') + await alixGroup.send('gm') + + // bo's num groups == 1 + await boClient.conversations.sync() + boGroups = await boClient.conversations.listGroups() + if (boGroups.length !== 1) { + throw new Error( + 'num groups for bo should be 1, but it is' + boGroups.length + ) + } + + // caro's num groups == 1 + await caroClient.conversations.sync() + caroGroups = await caroClient.conversations.listGroups() + if (caroGroups.length !== 1) { + throw new Error( + 'num groups for caro should be 1, but it is' + caroGroups.length + ) + } + + await caroGroups[0].sync() + if (!caroGroups[0].isActive()) { + throw new Error('caros group should be active') + } + + await alixGroup.removeMembers([caroClient.address]) + await alixGroup.sync() + const alixGroupMembers = await alixGroup.memberInboxIds() + if (alixGroupMembers.length !== 2) { + throw new Error( + 'num group members should be 2 but was' + alixGroupMembers.length + ) + } + + await caroGroups[0].sync() + if (await caroGroups[0].isActive()) { + throw new Error('caros group should not be active') + } + + const caroGroupMembers = await caroGroups[0].memberInboxIds() + if (caroGroupMembers.length !== 2) { + throw new Error( + 'num group members should be 2 but was' + caroGroupMembers.length + ) + } + + return true +}) + +test('can remove and add members from a group by inbox id', async () => { + // Create three MLS enabled Clients + const [alixClient, boClient, caroClient] = await createClients(3) + + // alix creates a group + const alixGroup = await alixClient.conversations.newGroup([ + boClient.address, + caroClient.address, + ]) + + // alix can confirm memberInboxIds + await alixGroup.sync() + const memberInboxIds = await alixGroup.memberInboxIds() + if (memberInboxIds.length !== 3) { + throw new Error('num group members should be 3') + } + + await alixGroup.removeMembersByInboxId([caroClient.inboxId]) + await alixGroup.sync() + const alixGroupMembers = await alixGroup.memberInboxIds() + if (alixGroupMembers.length !== 2) { + throw new Error('num group members should be 2') + } + + await alixGroup.addMembersByInboxId([caroClient.inboxId]) + await alixGroup.sync() + const alixGroupMembers2 = await alixGroup.memberInboxIds() + if (alixGroupMembers2.length !== 3) { + throw new Error('num group members should be 3') + } + + return true +}) + +test('can stream both groups and messages at same time', async () => { + const [alix, bo] = await createClients(2) + + let groupCallbacks = 0 + let messageCallbacks = 0 + await bo.conversations.stream(async () => { + groupCallbacks++ + }) + + await bo.conversations.streamAllMessages(async () => { + messageCallbacks++ + }) + + const group = await alix.conversations.newGroup([bo.address]) + await group.send('hello') + + await delayToPropogate() + // await new Promise((resolve) => setTimeout(resolve, 10000)) + assert( + messageCallbacks === 1, + 'message stream should have received 1 message' + ) + assert(groupCallbacks === 1, 'group stream should have received 1 group') + return true +}) + +test('can stream groups', async () => { + const [alixClient, boClient, caroClient] = await createClients(3) + + // Start streaming groups + const groups: Conversation[] = [] + await alixClient.conversations.stream(async (group: Conversation) => { + groups.push(group) + }, 'groups') + + // caro creates a group with alix, so stream callback is fired + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const caroGroup = await caroClient.conversations.newGroup([ + alixClient.address, + ]) + await delayToPropogate() + if ((groups.length as number) !== 1) { + throw Error('Unexpected num groups (should be 1): ' + groups.length) + } + + assert((await groups[0].members()).length === 2, 'should be 2') + + // bo creates a group with alix so a stream callback is fired + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const boGroup = await boClient.conversations.newGroup([alixClient.address]) + await delayToPropogate() + if ((groups.length as number) !== 2) { + throw Error('Unexpected num groups (should be 2): ' + groups.length) + } + + // * Note alix creating a group does not trigger alix conversations + // group stream. Workaround is to sync after you create and list manually + // See https://github.com/xmtp/libxmtp/issues/504 + + // alix creates a group + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const alixGroup = await alixClient.conversations.newGroup([ + boClient.address, + caroClient.address, + ]) + await delayToPropogate() + if (groups.length !== 3) { + throw Error('Expected group length 3 but it is: ' + groups.length) + } + // Sync groups after creation if you created a group + const listedGroups = await alixClient.conversations.listGroups() + await delayToPropogate() + groups.push(listedGroups[listedGroups.length - 1]) + if ((groups.length as number) !== 4) { + throw Error('Expected group length 4 but it is: ' + groups.length) + } + + alixClient.conversations.cancelStream() + await delayToPropogate() + + // Creating a group should no longer trigger stream groups + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const caroSecond = await caroClient.conversations.newGroup([ + alixClient.address, + ]) + await delayToPropogate() + if ((groups.length as number) !== 4) { + throw Error('Unexpected num groups (should be 4): ' + groups.length) + } + + return true +}) + +test('can filter groups by consent', async () => { + const [alixClient, boClient, caroClient] = await createClients(3) + + const boGroup1 = await boClient.conversations.newGroup([alixClient.address]) + const otherGroup = await alixClient.conversations.newGroup([boClient.address]) + await boClient.conversations.findOrCreateDm(alixClient.address) + await caroClient.conversations.findOrCreateDm(boClient.address) + await boClient.conversations.sync() + await boClient.conversations.findDmByInboxId(caroClient.inboxId) + const boGroup2 = await boClient.conversations.findGroup(otherGroup.id) + + const boConvos = await boClient.conversations.listGroups() + const boConvosFilteredAllowed = await boClient.conversations.listGroups( + {}, + undefined, + ['allowed'] + ) + const boConvosFilteredUnknown = await boClient.conversations.listGroups( + {}, + undefined, + ['unknown'] + ) + + assert( + boConvos.length === 2, + `Conversation length should be 2 but was ${boConvos.length}` + ) + + assert( + boConvosFilteredAllowed + .map((conversation: any) => conversation.id) + .toString() === [boGroup1.id].toString(), + `Conversation allowed should be ${[ + boGroup1.id, + ].toString()} but was ${boConvosFilteredAllowed + .map((convo: any) => convo.id) + .toString()}` + ) + + assert( + boConvosFilteredUnknown + .map((conversation: any) => conversation.id) + .toString() === [boGroup2?.id].toString(), + `Conversation unknown filter should be ${[ + boGroup2?.id, + ].toString()} but was ${boConvosFilteredUnknown + .map((convo: any) => convo.id) + .toString()}` + ) + + return true +}) + +test('can list groups with params', async () => { + const [alixClient, boClient] = await createClients(2) + + const boGroup1 = await boClient.conversations.newGroup([alixClient.address]) + const boGroup2 = await boClient.conversations.newGroup([alixClient.address]) + + await boGroup1.send({ text: `first message` }) + await boGroup1.send({ text: `second message` }) + await boGroup1.send({ text: `third message` }) + await boGroup2.send({ text: `first message` }) + + const boGroupsOrderLastMessage = await boClient.conversations.listGroups({ + lastMessage: true, + }) + const boGroupsLimit = await boClient.conversations.listGroups({}, 1) + + assert( + boGroupsOrderLastMessage.map((group: any) => group.id).toString() === + [boGroup2.id, boGroup1.id].toString(), + `Group order should be group2 then group1 but was ${boGroupsOrderLastMessage + .map((group: any) => group.id) + .toString()}` + ) + + const messages = await boGroupsOrderLastMessage[0].messages() + assert( + messages[0].content() === 'first message', + `last message should be first message ${messages[0].content()}` + ) + assert( + boGroupsOrderLastMessage[0].lastMessage?.content() === 'first message', + `last message should be last message ${boGroupsOrderLastMessage[0].lastMessage?.content()}` + ) + assert( + boGroupsLimit.length === 1, + `List length should be 1 but was ${boGroupsLimit.length}` + ) + + return true +}) + +test('can list groups', async () => { + const [alixClient, boClient] = await createClients(2) + + const group1 = await boClient.conversations.newGroup([alixClient.address], { + name: 'group1 name', + imageUrlSquare: 'www.group1image.com', + }) + const group2 = await boClient.conversations.newGroup([alixClient.address], { + name: 'group2 name', + imageUrlSquare: 'www.group2image.com', + }) + + const boGroups = await boClient.conversations.listGroups() + await alixClient.conversations.sync() + const alixGroups = await alixClient.conversations.listGroups() + + assert( + boGroups.length === alixGroups.length, + `group lengths should be the same but bo was ${boGroups.length} and alix was ${alixGroups.length}` + ) + + const boGroup1 = await boClient.conversations.findGroup(group1.id) + const boGroup2 = await boClient.conversations.findGroup(group2.id) + + const alixGroup1 = await alixClient.conversations.findGroup(group1.id) + const alixGroup2 = await alixClient.conversations.findGroup(group2.id) + + assert( + boGroup2?.name === 'group2 name', + `Group 2 name for bo should be group2 name but was ${boGroup2?.name}` + ) + + assert( + boGroup1?.imageUrlSquare === 'www.group1image.com', + `Group 2 url for bo should be www.group1image.com but was ${boGroup1?.imageUrlSquare}` + ) -// await boGroups[0].sync() -// const boGroupMembers = await boGroups[0].memberInboxIds() -// if (boGroupMembers.length !== 3) { -// throw new Error('num group members should be 3') -// } + assert( + alixGroup1?.name === 'group1 name', + `Group 1 name for alix should be group1 name but was ${alixGroup1?.name}` + ) -// return true -// }) + assert( + alixGroup2?.imageUrlSquare === 'www.group2image.com', + `Group 2 url for alix should be www.group2image.com but was ${alixGroup2?.imageUrlSquare}` + ) -// test('can remove members from a group', async () => { -// // Create three MLS enabled Clients -// const [alixClient, boClient, caroClient] = await createClients(3) + assert(boGroup1?.isGroupActive === true, `Group 1 should be active for bo`) -// // alix's num groups start at 0 -// let alixGroups = await alixClient.conversations.listGroups() -// if (alixGroups.length !== 0) { -// throw new Error('num groups should be 0') -// } + return true +}) -// // bo's num groups start at 0 -// let boGroups = await boClient.conversations.listGroups() -// assert(boGroups.length === 0, 'num groups should be 0') +test('can stream groups and messages', async () => { + const [alixClient, boClient] = await createClients(2) -// // caro's num groups start at 0 -// let caroGroups = await caroClient.conversations.listGroups() -// if (caroGroups.length !== 0) { -// throw new Error('num groups should be 0') -// } + // Start streaming groups + const groups: Conversation[] = [] + await alixClient.conversations.stream(async (group: Conversation) => { + groups.push(group) + }) + // Stream messages twice + await alixClient.conversations.streamAllMessages(async (message) => {}) + await alixClient.conversations.streamAllMessages(async (message) => {}) + + // bo creates a group with alix so a stream callback is fired + // eslint-disable-next-line @typescript-eslint/no-unused-vars + await boClient.conversations.newGroup([alixClient.address]) + await delayToPropogate() + if ((groups.length as number) !== 1) { + throw Error(`Unexpected num groups (should be 1): ${groups.length}`) + } -// // alix creates a group -// const alixGroup = await alixClient.conversations.newGroup([ -// boClient.address, -// caroClient.address, -// ]) - -// // alix's num groups == 1 -// await alixClient.conversations.sync() -// alixGroups = await alixClient.conversations.listGroups() -// if (alixGroups.length !== 1) { -// throw new Error('num groups should be 1') -// } + return true +}) -// // alix can confirm memberInboxIds -// await alixGroup.sync() -// const memberInboxIds = await alixGroup.memberInboxIds() -// if (memberInboxIds.length !== 3) { -// throw new Error('num group members should be 3') -// } -// if ( -// !( -// memberInboxIds.includes(alixClient.inboxId) && -// memberInboxIds.includes(boClient.inboxId) -// ) -// ) { -// throw new Error('missing address') -// } +test('canMessage', async () => { + const [alix, caro] = await createClients(3) -// // alix can send messages -// await alixGroup.send('hello, world') -// await alixGroup.send('gm') - -// // bo's num groups == 1 -// await boClient.conversations.sync() -// boGroups = await boClient.conversations.listGroups() -// if (boGroups.length !== 1) { -// throw new Error( -// 'num groups for bo should be 1, but it is' + boGroups.length -// ) -// } + const canMessageV3 = await caro.canMessage([ + caro.address, + alix.address, + '0x4E9ce36E442e55EcD9025B9a6E0D88485d628A67', + ]) -// // caro's num groups == 1 -// await caroClient.conversations.sync() -// caroGroups = await caroClient.conversations.listGroups() -// if (caroGroups.length !== 1) { -// throw new Error( -// 'num groups for caro should be 1, but it is' + caroGroups.length -// ) -// } + assert( + canMessageV3['0x4E9ce36E442e55EcD9025B9a6E0D88485d628A67'.toLowerCase()] === + false, + `should not be able to message 0x4E9ce36E442e55EcD9025B9a6E0D88485d628A67` + ) -// await caroGroups[0].sync() -// if (!caroGroups[0].isActive()) { -// throw new Error('caros group should be active') -// } + assert( + canMessageV3[caro.address.toLowerCase()] === true, + `should be able to message ${caro.address}` + ) -// await alixGroup.removeMembers([caroClient.address]) -// await alixGroup.sync() -// const alixGroupMembers = await alixGroup.memberInboxIds() -// if (alixGroupMembers.length !== 2) { -// throw new Error( -// 'num group members should be 2 but was' + alixGroupMembers.length -// ) -// } + assert( + canMessageV3[alix.address.toLowerCase()] === true, + `should be able to message ${alix.address}` + ) -// await caroGroups[0].sync() -// if (await caroGroups[0].isActive()) { -// throw new Error('caros group should not be active') -// } + return true +}) -// const caroGroupMembers = await caroGroups[0].memberInboxIds() -// if (caroGroupMembers.length !== 2) { -// throw new Error( -// 'num group members should be 2 but was' + caroGroupMembers.length -// ) -// } +test('can stream group messages', async () => { + // Create three MLS enabled Clients + const [alixClient, boClient, caroClient] = await createClients(3) + + // alix creates a group + const alixGroup = await alixClient.conversations.newGroup([ + boClient.address, + caroClient.address, + ]) + + // Record message stream for this group + const groupMessages: DecodedMessage[] = [] + const cancelGroupMessageStream = await alixGroup.streamMessages( + async (message) => { + groupMessages.push(message) + } + ) -// return true -// }) + // bo's num groups == 1 + await boClient.conversations.sync() + const boGroup = (await boClient.conversations.listGroups())[0] -// test('can remove and add members from a group by inbox id', async () => { -// // Create three MLS enabled Clients -// const [alixClient, boClient, caroClient] = await createClients(3) - -// // alix creates a group -// const alixGroup = await alixClient.conversations.newGroup([ -// boClient.address, -// caroClient.address, -// ]) - -// // alix can confirm memberInboxIds -// await alixGroup.sync() -// const memberInboxIds = await alixGroup.memberInboxIds() -// if (memberInboxIds.length !== 3) { -// throw new Error('num group members should be 3') -// } + for (let i = 0; i < 5; i++) { + await boGroup.send({ text: `Message ${i}` }) + await delayToPropogate() + } -// await alixGroup.removeMembersByInboxId([caroClient.inboxId]) -// await alixGroup.sync() -// const alixGroupMembers = await alixGroup.memberInboxIds() -// if (alixGroupMembers.length !== 2) { -// throw new Error('num group members should be 2') -// } + if (groupMessages.length !== 5) { + throw Error('Unexpected convo messages count ' + groupMessages.length) + } + for (let i = 0; i < 5; i++) { + if (groupMessages[i].content() !== `Message ${i}`) { + throw Error( + 'Unexpected group message content ' + groupMessages[i].content() + ) + } + } -// await alixGroup.addMembersByInboxId([caroClient.inboxId]) -// await alixGroup.sync() -// const alixGroupMembers2 = await alixGroup.memberInboxIds() -// if (alixGroupMembers2.length !== 3) { -// throw new Error('num group members should be 3') -// } + cancelGroupMessageStream() + for (let i = 0; i < 5; i++) { + await boGroup.send({ text: `Message ${i}` }) + } -// return true -// }) + if (groupMessages.length !== 5) { + throw Error('Unexpected convo messages count ' + groupMessages.length) + } -// test('can stream both groups and messages at same time', async () => { -// const [alix, bo] = await createClients(2) + return true +}) -// let groupCallbacks = 0 -// let messageCallbacks = 0 -// await bo.conversations.stream(async () => { -// groupCallbacks++ -// }) +test('can make a group with metadata', async () => { + const [alix, bo] = await createClients(2) + Client.register(new GroupUpdatedCodec()) -// await bo.conversations.streamAllMessages(async () => { -// messageCallbacks++ -// }) + const alixGroup = await alix.conversations.newGroup([bo.address], { + name: 'Start Name', + imageUrlSquare: 'starturl.com', + description: 'a fun description', + }) -// const group = await alix.conversations.newGroup([bo.address]) -// await group.send('hello') + const groupName1 = await alixGroup.groupName() + const groupImageUrl1 = await alixGroup.groupImageUrlSquare() + const groupDescription1 = await alixGroup.groupDescription() + assert( + groupName1 === 'Start Name', + `the group should start with a name of Start Name not ${groupName1}` + ) -// await delayToPropogate() -// // await new Promise((resolve) => setTimeout(resolve, 10000)) -// assert( -// messageCallbacks === 1, -// 'message stream should have received 1 message' -// ) -// assert(groupCallbacks === 1, 'group stream should have received 1 group') -// return true -// }) + assert( + groupImageUrl1 === 'starturl.com', + `the group should start with a name of starturl.com not ${groupImageUrl1}` + ) -// test('can stream groups', async () => { -// const [alixClient, boClient, caroClient] = await createClients(3) + assert( + groupDescription1 === 'a fun description', + `the group should start with a name of a fun description not ${groupDescription1}` + ) -// // Start streaming groups -// const groups: Conversation[] = [] -// await alixClient.conversations.stream(async (group: Conversation) => { -// groups.push(group) -// }, 'groups') + await alixGroup.updateGroupName('New Name') + await alixGroup.updateGroupImageUrlSquare('newurl.com') + await alixGroup.updateGroupDescription('a new group description') + await alixGroup.sync() + await bo.conversations.sync() + const boGroups = await bo.conversations.listGroups() + const boGroup = boGroups[0] + await boGroup.sync() -// // caro creates a group with alix, so stream callback is fired -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// const caroGroup = await caroClient.conversations.newGroup([ -// alixClient.address, -// ]) -// await delayToPropogate() -// if ((groups.length as number) !== 1) { -// throw Error('Unexpected num groups (should be 1): ' + groups.length) -// } + const groupName2 = await alixGroup.groupName() + const groupImageUrl2 = await alixGroup.groupImageUrlSquare() + const groupDescription2 = await alixGroup.groupDescription() + assert( + groupName2 === 'New Name', + `the group should start with a name of New Name not ${groupName2}` + ) -// assert((await groups[0].members()).length === 2, 'should be 2') + assert( + groupImageUrl2 === 'newurl.com', + `the group should start with a name of newurl.com not ${groupImageUrl2}` + ) -// // bo creates a group with alix so a stream callback is fired -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// const boGroup = await boClient.conversations.newGroup([alixClient.address]) -// await delayToPropogate() -// if ((groups.length as number) !== 2) { -// throw Error('Unexpected num groups (should be 2): ' + groups.length) -// } + assert( + groupDescription2 === 'a new group description', + `the group should start with a name of a new group description not ${groupDescription2}` + ) -// // * Note alix creating a group does not trigger alix conversations -// // group stream. Workaround is to sync after you create and list manually -// // See https://github.com/xmtp/libxmtp/issues/504 + const groupName3 = await boGroup.groupName() + const groupImageUrl3 = await boGroup.groupImageUrlSquare() + assert( + groupName3 === 'New Name', + `the group should start with a name of New Name not ${groupName3}` + ) -// // alix creates a group -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// const alixGroup = await alixClient.conversations.newGroup([ -// boClient.address, -// caroClient.address, -// ]) -// await delayToPropogate() -// if (groups.length !== 3) { -// throw Error('Expected group length 3 but it is: ' + groups.length) -// } -// // Sync groups after creation if you created a group -// const listedGroups = await alixClient.conversations.listGroups() -// await delayToPropogate() -// groups.push(listedGroups[listedGroups.length - 1]) -// if ((groups.length as number) !== 4) { -// throw Error('Expected group length 4 but it is: ' + groups.length) -// } + assert( + groupImageUrl3 === 'newurl.com', + `the group should start with a name of newurl.com not ${groupImageUrl3}` + ) -// alixClient.conversations.cancelStream() -// await delayToPropogate() + const boMessages = await boGroup.messages() + assert( + boMessages[0].contentTypeId === 'xmtp.org/group_updated:1.0', + 'Unexpected message content ' + JSON.stringify(boMessages[0].contentTypeId) + ) -// // Creating a group should no longer trigger stream groups -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// const caroSecond = await caroClient.conversations.newGroup([ -// alixClient.address, -// ]) -// await delayToPropogate() -// if ((groups.length as number) !== 4) { -// throw Error('Unexpected num groups (should be 4): ' + groups.length) -// } + const message = boMessages[1].content() as GroupUpdatedContent + assert( + message.metadataFieldsChanged[0].fieldName === 'group_image_url_square', + `the metadata field changed should be group_image_url_square but was ${message.metadataFieldsChanged[0].fieldName}` + ) + const message2 = boMessages[0].content() as GroupUpdatedContent + assert( + message2.metadataFieldsChanged[0].fieldName === 'description', + `the metadata field changed should be description but was ${message2.metadataFieldsChanged[0].fieldName}` + ) + return true +}) -// return true -// }) +test('can make a group with admin permissions', async () => { + const [adminClient, anotherClient] = await createClients(2) -// test('can filter groups by consent', async () => { -// const [alixClient, boClient, caroClient] = await createClients(3) - -// const boGroup1 = await boClient.conversations.newGroup([alixClient.address]) -// const otherGroup = await alixClient.conversations.newGroup([boClient.address]) -// await boClient.conversations.findOrCreateDm(alixClient.address) -// await caroClient.conversations.findOrCreateDm(boClient.address) -// await boClient.conversations.sync() -// await boClient.conversations.findDmByInboxId(caroClient.inboxId) -// const boGroup2 = await boClient.conversations.findGroup(otherGroup.id) - -// const boConvos = await boClient.conversations.listGroups() -// const boConvosFilteredAllowed = await boClient.conversations.listGroups( -// {}, -// undefined, -// ['allowed'] -// ) -// const boConvosFilteredUnknown = await boClient.conversations.listGroups( -// {}, -// undefined, -// ['unknown'] -// ) - -// assert( -// boConvos.length === 2, -// `Conversation length should be 2 but was ${boConvos.length}` -// ) - -// assert( -// boConvosFilteredAllowed -// .map((conversation: any) => conversation.id) -// .toString() === [boGroup1.id].toString(), -// `Conversation allowed should be ${[ -// boGroup1.id, -// ].toString()} but was ${boConvosFilteredAllowed -// .map((convo: any) => convo.id) -// .toString()}` -// ) - -// assert( -// boConvosFilteredUnknown -// .map((conversation: any) => conversation.id) -// .toString() === [boGroup2?.id].toString(), -// `Conversation unknown filter should be ${[ -// boGroup2?.id, -// ].toString()} but was ${boConvosFilteredUnknown -// .map((convo: any) => convo.id) -// .toString()}` -// ) + const group = await adminClient.conversations.newGroup( + [anotherClient.address], + { permissionLevel: 'admin_only' } + ) -// return true -// }) + if ((await group.permissionPolicySet()).addMemberPolicy !== 'admin') { + throw Error( + `Group permission level should be admin but was ${ + (await group.permissionPolicySet()).addMemberPolicy + }` + ) + } -// test('can list groups with params', async () => { -// const [alixClient, boClient] = await createClients(2) - -// const boGroup1 = await boClient.conversations.newGroup([alixClient.address]) -// const boGroup2 = await boClient.conversations.newGroup([alixClient.address]) - -// await boGroup1.send({ text: `first message` }) -// await boGroup1.send({ text: `second message` }) -// await boGroup1.send({ text: `third message` }) -// await boGroup2.send({ text: `first message` }) - -// const boGroupsOrderLastMessage = await boClient.conversations.listGroups({ -// lastMessage: true, -// }) -// const boGroupsLimit = await boClient.conversations.listGroups({}, 1) - -// assert( -// boGroupsOrderLastMessage.map((group: any) => group.id).toString() === -// [boGroup2.id, boGroup1.id].toString(), -// `Group order should be group2 then group1 but was ${boGroupsOrderLastMessage -// .map((group: any) => group.id) -// .toString()}` -// ) - -// const messages = await boGroupsOrderLastMessage[0].messages() -// assert( -// messages[0].content() === 'first message', -// `last message should be first message ${messages[0].content()}` -// ) -// assert( -// boGroupsOrderLastMessage[0].lastMessage?.content() === 'first message', -// `last message should be last message ${boGroupsOrderLastMessage[0].lastMessage?.content()}` -// ) -// assert( -// boGroupsLimit.length === 1, -// `List length should be 1 but was ${boGroupsLimit.length}` -// ) + const isSuperAdmin = await group.isSuperAdmin(adminClient.inboxId) + if (!isSuperAdmin) { + throw Error(`adminClient should be the super admin`) + } -// return true -// }) + // Creator id not working, see https://github.com/xmtp/libxmtp/issues/788 + // if (group.creatorInboxId !== adminClient.inboxId) { + // throw Error( + // `adminClient should be the creator but was ${group.creatorInboxId}` + // ) + // } -// test('can list groups', async () => { -// const [alixClient, boClient] = await createClients(2) + return true +}) -// const group1 = await boClient.conversations.newGroup([alixClient.address], { -// name: 'group1 name', -// imageUrlSquare: 'www.group1image.com', -// }) -// const group2 = await boClient.conversations.newGroup([alixClient.address], { -// name: 'group2 name', -// imageUrlSquare: 'www.group2image.com', -// }) +test('can paginate group messages', async () => { + // Create three MLS enabled Clients + const [alixClient, boClient] = await createClients(2) -// const boGroups = await boClient.conversations.listGroups() -// await alixClient.conversations.sync() -// const alixGroups = await alixClient.conversations.listGroups() + // alix creates a group + const alixGroup = await alixClient.conversations.newGroup([boClient.address]) -// assert( -// boGroups.length === alixGroups.length, -// `group lengths should be the same but bo was ${boGroups.length} and alix was ${alixGroups.length}` -// ) + // alix can send messages + await alixGroup.send('hello, world') + await alixGroup.send('gm') -// const boGroup1 = await boClient.conversations.findGroup(group1.id) -// const boGroup2 = await boClient.conversations.findGroup(group2.id) + await boClient.conversations.sync() + const boGroups = await boClient.conversations.listGroups() + if (boGroups.length !== 1) { + throw new Error( + 'num groups for bo should be 1, but it is' + boGroups.length + ) + } + await delayToPropogate() + // bo can read messages from alix + await boGroups[0].sync() + const boMessages: DecodedMessage[] = await boGroups[0].messages({ + limit: 1, + }) -// const alixGroup1 = await alixClient.conversations.findGroup(group1.id) -// const alixGroup2 = await alixClient.conversations.findGroup(group2.id) + if (boMessages.length !== 1) { + throw Error(`Should limit just 1 message but was ${boMessages.length}`) + } -// assert( -// boGroup2?.name === 'group2 name', -// `Group 2 name for bo should be group2 name but was ${boGroup2?.name}` -// ) + return true +}) -// assert( -// boGroup1?.imageUrlSquare === 'www.group1image.com', -// `Group 2 url for bo should be www.group1image.com but was ${boGroup1?.imageUrlSquare}` -// ) +test('can stream all group messages', async () => { + const [alix, bo, caro] = await createClients(3) -// assert( -// alixGroup1?.name === 'group1 name', -// `Group 1 name for alix should be group1 name but was ${alixGroup1?.name}` -// ) + await delayToPropogate() -// assert( -// alixGroup2?.imageUrlSquare === 'www.group2image.com', -// `Group 2 url for alix should be www.group2image.com but was ${alixGroup2?.imageUrlSquare}` -// ) + // Start bo starts a new group. + const boGroup = await bo.conversations.newGroup([alix.address]) + await delayToPropogate() -// assert(boGroup1?.isGroupActive === true, `Group 1 should be active for bo`) + // Starts a new conversation. + const caroGroup = await caro.conversations.newGroup([alix.address]) -// return true -// }) + // Record message stream across all conversations + const allMessages: DecodedMessage[] = [] + // If we don't call syncConversations here, the streamAllGroupMessages will not + // stream the first message. Feels like a bug. + await alix.conversations.sync() + await alix.conversations.streamAllMessages(async (message) => { + allMessages.push(message) + }, 'groups') -// test('can stream groups and messages', async () => { -// const [alixClient, boClient] = await createClients(2) - -// // Start streaming groups -// const groups: Conversation[] = [] -// await alixClient.conversations.stream(async (group: Conversation) => { -// groups.push(group) -// }) -// // Stream messages twice -// await alixClient.conversations.streamAllMessages(async (message) => {}) -// await alixClient.conversations.streamAllMessages(async (message) => {}) - -// // bo creates a group with alix so a stream callback is fired -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// await boClient.conversations.newGroup([alixClient.address]) -// await delayToPropogate() -// if ((groups.length as number) !== 1) { -// throw Error(`Unexpected num groups (should be 1): ${groups.length}`) -// } + for (let i = 0; i < 5; i++) { + await boGroup.send({ text: `Message ${i}` }) + await delayToPropogate() + } -// return true -// }) + const count = allMessages.length + if (count !== 5) { + throw Error('Unexpected all messages count first' + allMessages.length) + } -// test('canMessage', async () => { -// const [alix, caro] = await createClients(3) + await delayToPropogate() + for (let i = 0; i < 5; i++) { + await caroGroup.send({ text: `Message ${i}` }) + await delayToPropogate() + } -// const canMessageV3 = await caro.canMessage([ -// caro.address, -// alix.address, -// '0x4E9ce36E442e55EcD9025B9a6E0D88485d628A67', -// ]) + if (allMessages.length !== 10) { + throw Error('Unexpected all messages count second' + allMessages.length) + } -// assert( -// canMessageV3['0x4E9ce36E442e55EcD9025B9a6E0D88485d628A67'.toLowerCase()] === -// false, -// `should not be able to message 0x4E9ce36E442e55EcD9025B9a6E0D88485d628A67` -// ) + alix.conversations.cancelStreamAllMessages() + await delayToPropogate() + await alix.conversations.streamAllMessages(async (message) => { + allMessages.push(message) + }) -// assert( -// canMessageV3[caro.address.toLowerCase()] === true, -// `should be able to message ${caro.address}` -// ) + for (let i = 0; i < 5; i++) { + await boGroup.send({ text: `Message ${i}` }) + await delayToPropogate() + } + if (allMessages.length <= 10) { + throw Error('Unexpected all messages count ' + allMessages.length) + } -// assert( -// canMessageV3[alix.address.toLowerCase()] === true, -// `should be able to message ${alix.address}` -// ) + return true +}) -// return true -// }) +test('creating a group should allow group', async () => { + const [alix, bo] = await createClients(2) -// test('can stream group messages', async () => { -// // Create three MLS enabled Clients -// const [alixClient, boClient, caroClient] = await createClients(3) - -// // alix creates a group -// const alixGroup = await alixClient.conversations.newGroup([ -// boClient.address, -// caroClient.address, -// ]) - -// // Record message stream for this group -// const groupMessages: DecodedMessage[] = [] -// const cancelGroupMessageStream = await alixGroup.streamMessages( -// async (message) => { -// groupMessages.push(message) -// } -// ) - -// // bo's num groups == 1 -// await boClient.conversations.sync() -// const boGroup = (await boClient.conversations.listGroups())[0] - -// for (let i = 0; i < 5; i++) { -// await boGroup.send({ text: `Message ${i}` }) -// await delayToPropogate() -// } + const group = await alix.conversations.newGroup([bo.address]) + await alix.conversations.sync() + const consent = await alix.preferences.conversationConsentState(group.id) + const groupConsent = await group.consentState() -// if (groupMessages.length !== 5) { -// throw Error('Unexpected convo messages count ' + groupMessages.length) -// } -// for (let i = 0; i < 5; i++) { -// if (groupMessages[i].content() !== `Message ${i}`) { -// throw Error( -// 'Unexpected group message content ' + groupMessages[i].content() -// ) -// } -// } + if (consent !== groupConsent) { + throw Error('Group should be allowed') + } -// cancelGroupMessageStream() -// for (let i = 0; i < 5; i++) { -// await boGroup.send({ text: `Message ${i}` }) -// } + assert( + groupConsent === 'allowed', + `the message should have a consent state of allowed but was ${groupConsent}` + ) -// if (groupMessages.length !== 5) { -// throw Error('Unexpected convo messages count ' + groupMessages.length) -// } + return true +}) -// return true -// }) +test('can group consent', async () => { + const [alix, bo] = await createClients(2) + const group = await bo.conversations.newGroup([alix.address]) + await alix.conversations.sync() + let isAllowed = await alix.preferences.conversationConsentState(group.id) + assert( + isAllowed !== 'allowed', + `alix group should NOT be allowed but was ${isAllowed}` + ) -// test('can make a group with metadata', async () => { -// const [alix, bo] = await createClients(2) -// Client.register(new GroupUpdatedCodec()) - -// const alixGroup = await alix.conversations.newGroup([bo.address], { -// name: 'Start Name', -// imageUrlSquare: 'starturl.com', -// description: 'a fun description', -// }) - -// const groupName1 = await alixGroup.groupName() -// const groupImageUrl1 = await alixGroup.groupImageUrlSquare() -// const groupDescription1 = await alixGroup.groupDescription() -// assert( -// groupName1 === 'Start Name', -// `the group should start with a name of Start Name not ${groupName1}` -// ) - -// assert( -// groupImageUrl1 === 'starturl.com', -// `the group should start with a name of starturl.com not ${groupImageUrl1}` -// ) - -// assert( -// groupDescription1 === 'a fun description', -// `the group should start with a name of a fun description not ${groupDescription1}` -// ) - -// await alixGroup.updateGroupName('New Name') -// await alixGroup.updateGroupImageUrlSquare('newurl.com') -// await alixGroup.updateGroupDescription('a new group description') -// await alixGroup.sync() -// await bo.conversations.sync() -// const boGroups = await bo.conversations.listGroups() -// const boGroup = boGroups[0] -// await boGroup.sync() - -// const groupName2 = await alixGroup.groupName() -// const groupImageUrl2 = await alixGroup.groupImageUrlSquare() -// const groupDescription2 = await alixGroup.groupDescription() -// assert( -// groupName2 === 'New Name', -// `the group should start with a name of New Name not ${groupName2}` -// ) - -// assert( -// groupImageUrl2 === 'newurl.com', -// `the group should start with a name of newurl.com not ${groupImageUrl2}` -// ) - -// assert( -// groupDescription2 === 'a new group description', -// `the group should start with a name of a new group description not ${groupDescription2}` -// ) - -// const groupName3 = await boGroup.groupName() -// const groupImageUrl3 = await boGroup.groupImageUrlSquare() -// assert( -// groupName3 === 'New Name', -// `the group should start with a name of New Name not ${groupName3}` -// ) - -// assert( -// groupImageUrl3 === 'newurl.com', -// `the group should start with a name of newurl.com not ${groupImageUrl3}` -// ) - -// const boMessages = await boGroup.messages() -// assert( -// boMessages[0].contentTypeId === 'xmtp.org/group_updated:1.0', -// 'Unexpected message content ' + JSON.stringify(boMessages[0].contentTypeId) -// ) - -// const message = boMessages[1].content() as GroupUpdatedContent -// assert( -// message.metadataFieldsChanged[0].fieldName === 'group_image_url_square', -// `the metadata field changed should be group_image_url_square but was ${message.metadataFieldsChanged[0].fieldName}` -// ) -// const message2 = boMessages[0].content() as GroupUpdatedContent -// assert( -// message2.metadataFieldsChanged[0].fieldName === 'description', -// `the metadata field changed should be description but was ${message2.metadataFieldsChanged[0].fieldName}` -// ) -// return true -// }) + isAllowed = await bo.preferences.conversationConsentState(group.id) + assert( + isAllowed === 'allowed', + `bo group should be allowed but was ${isAllowed}` + ) + assert( + (await group.state) === 'allowed', + `the group should have a consent state of allowed but was ${await group.state}` + ) -// test('can make a group with admin permissions', async () => { -// const [adminClient, anotherClient] = await createClients(2) + await bo.preferences.setConsentState( + new ConsentRecord(group.id, 'conversation_id', 'denied') + ) + const isDenied = await bo.preferences.conversationConsentState(group.id) + assert(isDenied === 'denied', `bo group should be denied but was ${isDenied}`) + assert( + (await group.consentState()) === 'denied', + `the group should have a consent state of denied but was ${await group.consentState()}` + ) -// const group = await adminClient.conversations.newGroup( -// [anotherClient.address], -// { permissionLevel: 'admin_only' } -// ) + await group.updateConsent('allowed') + isAllowed = await bo.preferences.conversationConsentState(group.id) + assert( + isAllowed === 'allowed', + `bo group should be allowed2 but was ${isAllowed}` + ) + assert( + (await group.consentState()) === 'allowed', + `the group should have a consent state2 of allowed but was ${await group.consentState()}` + ) -// if ((await group.permissionPolicySet()).addMemberPolicy !== 'admin') { -// throw Error( -// `Group permission level should be admin but was ${ -// (await group.permissionPolicySet()).addMemberPolicy -// }` -// ) -// } + return true +}) -// const isSuperAdmin = await group.isSuperAdmin(adminClient.inboxId) -// if (!isSuperAdmin) { -// throw Error(`adminClient should be the super admin`) -// } +test('can allow and deny a inbox id', async () => { + const [alix, bo] = await createClients(2) + const boGroup = await bo.conversations.newGroup([alix.address]) -// // Creator id not working, see https://github.com/xmtp/libxmtp/issues/788 -// // if (group.creatorInboxId !== adminClient.inboxId) { -// // throw Error( -// // `adminClient should be the creator but was ${group.creatorInboxId}` -// // ) -// // } + let isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) + assert( + isInboxAllowed === 'unknown', + `isInboxAllowed should be unknown but was ${isInboxAllowed}` + ) -// return true -// }) + await bo.preferences.setConsentState( + new ConsentRecord(alix.inboxId, 'inbox_id', 'allowed') + ) -// test('can paginate group messages', async () => { -// // Create three MLS enabled Clients -// const [alixClient, boClient] = await createClients(2) + let alixMember = (await boGroup.members()).find( + (member) => member.inboxId === alix.inboxId + ) + assert( + alixMember?.consentState === 'allowed', + `alixMember should be allowed but was ${alixMember?.consentState}` + ) -// // alix creates a group -// const alixGroup = await alixClient.conversations.newGroup([boClient.address]) + isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) + assert( + isInboxAllowed === 'allowed', + `isInboxAllowed2 should be true but was ${isInboxAllowed}` + ) -// // alix can send messages -// await alixGroup.send('hello, world') -// await alixGroup.send('gm') + let isAddressAllowed = await bo.preferences.addressConsentState(alix.address) + assert( + isAddressAllowed === 'allowed', + `isAddressAllowed should be true but was ${isAddressAllowed}` + ) -// await boClient.conversations.sync() -// const boGroups = await boClient.conversations.listGroups() -// if (boGroups.length !== 1) { -// throw new Error( -// 'num groups for bo should be 1, but it is' + boGroups.length -// ) -// } -// await delayToPropogate() -// // bo can read messages from alix -// await boGroups[0].sync() -// const boMessages: DecodedMessage[] = await boGroups[0].messages({ -// limit: 1, -// }) - -// if (boMessages.length !== 1) { -// throw Error(`Should limit just 1 message but was ${boMessages.length}`) -// } + await bo.preferences.setConsentState( + new ConsentRecord(alix.inboxId, 'inbox_id', 'denied') + ) -// return true -// }) + alixMember = (await boGroup.members()).find( + (member) => member.inboxId === alix.inboxId + ) + assert( + alixMember?.consentState === 'denied', + `alixMember should be denied but was ${alixMember?.consentState}` + ) -// test('can stream all group messages', async () => { -// const [alix, bo, caro] = await createClients(3) + isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) + assert( + isInboxAllowed === 'denied', + `isInboxAllowed3 should be false but was ${isInboxAllowed}` + ) -// await delayToPropogate() + await bo.preferences.setConsentState( + new ConsentRecord(alix.address, 'address', 'allowed') + ) -// // Start bo starts a new group. -// const boGroup = await bo.conversations.newGroup([alix.address]) -// await delayToPropogate() + isAddressAllowed = await bo.preferences.addressConsentState(alix.address) + assert( + isAddressAllowed === 'allowed', + `isAddressAllowed2 should be true but was ${isAddressAllowed}` + ) + isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) + assert( + isInboxAllowed === 'allowed', + `isInboxAllowed4 should be false but was ${isInboxAllowed}` + ) -// // Starts a new conversation. -// const caroGroup = await caro.conversations.newGroup([alix.address]) - -// // Record message stream across all conversations -// const allMessages: DecodedMessage[] = [] -// // If we don't call syncConversations here, the streamAllGroupMessages will not -// // stream the first message. Feels like a bug. -// await alix.conversations.sync() -// await alix.conversations.streamAllMessages(async (message) => { -// allMessages.push(message) -// }, 'groups') - -// for (let i = 0; i < 5; i++) { -// await boGroup.send({ text: `Message ${i}` }) -// await delayToPropogate() -// } + return true +}) -// const count = allMessages.length -// if (count !== 5) { -// throw Error('Unexpected all messages count first' + allMessages.length) -// } +test('sync function behaves as expected', async () => { + const [alix, bo, caro] = await createClients(3) + const alixGroup = await alix.conversations.newGroup([bo.address]) -// await delayToPropogate() -// for (let i = 0; i < 5; i++) { -// await caroGroup.send({ text: `Message ${i}` }) -// await delayToPropogate() -// } + await alixGroup.send({ text: 'hello' }) -// if (allMessages.length !== 10) { -// throw Error('Unexpected all messages count second' + allMessages.length) -// } + // List groups will return empty until the first sync + let boGroups = await bo.conversations.listGroups() + assert(boGroups.length === 0, 'num groups for bo is 0 until we sync') -// alix.conversations.cancelStreamAllMessages() -// await delayToPropogate() -// await alix.conversations.streamAllMessages(async (message) => { -// allMessages.push(message) -// }) + await bo.conversations.sync() -// for (let i = 0; i < 5; i++) { -// await boGroup.send({ text: `Message ${i}` }) -// await delayToPropogate() -// } -// if (allMessages.length <= 10) { -// throw Error('Unexpected all messages count ' + allMessages.length) -// } + boGroups = await bo.conversations.listGroups() + assert(boGroups.length === 1, 'num groups for bo is 1') -// return true -// }) + // Num members will include the initial num of members even before sync + let numMembers = (await boGroups[0].memberInboxIds()).length + assert(numMembers === 2, 'num members should be 2') -// test('creating a group should allow group', async () => { -// const [alix, bo] = await createClients(2) + // Num messages for a group will be 0 until we sync the group + let numMessages = (await boGroups[0].messages()).length + assert(numMessages === 0, 'num members should be 1') -// const group = await alix.conversations.newGroup([bo.address]) -// await alix.conversations.sync() -// const consent = await alix.preferences.conversationConsentState(group.id) -// const groupConsent = await group.consentState() + await bo.conversations.sync() -// if (consent !== groupConsent) { -// throw Error('Group should be allowed') -// } + // Num messages is still 0 because we didnt sync the group itself + numMessages = (await boGroups[0].messages()).length + assert(numMessages === 0, 'num messages should be 0') -// assert( -// groupConsent === 'allowed', -// `the message should have a consent state of allowed but was ${groupConsent}` -// ) + await boGroups[0].sync() -// return true -// }) + // after syncing the group we now see the correct number of messages + numMessages = (await boGroups[0].messages()).length + assert(numMessages === 1, 'num members should be 1') -// test('can group consent', async () => { -// const [alix, bo] = await createClients(2) -// const group = await bo.conversations.newGroup([alix.address]) -// await alix.conversations.sync() -// let isAllowed = await alix.preferences.conversationConsentState(group.id) -// assert( -// isAllowed !== 'allowed', -// `alix group should NOT be allowed but was ${isAllowed}` -// ) - -// isAllowed = await bo.preferences.conversationConsentState(group.id) -// assert( -// isAllowed === 'allowed', -// `bo group should be allowed but was ${isAllowed}` -// ) -// assert( -// (await group.state) === 'allowed', -// `the group should have a consent state of allowed but was ${await group.state}` -// ) - -// await bo.preferences.setConsentState( -// new ConsentRecord(group.id, 'conversation_id', 'denied') -// ) -// const isDenied = await bo.preferences.conversationConsentState(group.id) -// assert(isDenied === 'denied', `bo group should be denied but was ${isDenied}`) -// assert( -// (await group.consentState()) === 'denied', -// `the group should have a consent state of denied but was ${await group.consentState()}` -// ) - -// await group.updateConsent('allowed') -// isAllowed = await bo.preferences.conversationConsentState(group.id) -// assert( -// isAllowed === 'allowed', -// `bo group should be allowed2 but was ${isAllowed}` -// ) -// assert( -// (await group.consentState()) === 'allowed', -// `the group should have a consent state2 of allowed but was ${await group.consentState()}` -// ) + await alixGroup.addMembers([caro.address]) -// return true -// }) + numMembers = (await boGroups[0].memberInboxIds()).length + assert(numMembers === 2, 'num members should be 2') -// test('can allow and deny a inbox id', async () => { -// const [alix, bo] = await createClients(2) -// const boGroup = await bo.conversations.newGroup([alix.address]) - -// let isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) -// assert( -// isInboxAllowed === 'unknown', -// `isInboxAllowed should be unknown but was ${isInboxAllowed}` -// ) - -// await bo.preferences.setConsentState( -// new ConsentRecord(alix.inboxId, 'inbox_id', 'allowed') -// ) - -// let alixMember = (await boGroup.members()).find( -// (member) => member.inboxId === alix.inboxId -// ) -// assert( -// alixMember?.consentState === 'allowed', -// `alixMember should be allowed but was ${alixMember?.consentState}` -// ) - -// isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) -// assert( -// isInboxAllowed === 'allowed', -// `isInboxAllowed2 should be true but was ${isInboxAllowed}` -// ) - -// let isAddressAllowed = await bo.preferences.addressConsentState(alix.address) -// assert( -// isAddressAllowed === 'allowed', -// `isAddressAllowed should be true but was ${isAddressAllowed}` -// ) - -// await bo.preferences.setConsentState( -// new ConsentRecord(alix.inboxId, 'inbox_id', 'denied') -// ) - -// alixMember = (await boGroup.members()).find( -// (member) => member.inboxId === alix.inboxId -// ) -// assert( -// alixMember?.consentState === 'denied', -// `alixMember should be denied but was ${alixMember?.consentState}` -// ) - -// isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) -// assert( -// isInboxAllowed === 'denied', -// `isInboxAllowed3 should be false but was ${isInboxAllowed}` -// ) - -// await bo.preferences.setConsentState( -// new ConsentRecord(alix.address, 'address', 'allowed') -// ) - -// isAddressAllowed = await bo.preferences.addressConsentState(alix.address) -// assert( -// isAddressAllowed === 'allowed', -// `isAddressAllowed2 should be true but was ${isAddressAllowed}` -// ) -// isInboxAllowed = await bo.preferences.inboxIdConsentState(alix.inboxId) -// assert( -// isInboxAllowed === 'allowed', -// `isInboxAllowed4 should be false but was ${isInboxAllowed}` -// ) + await bo.conversations.sync() -// return true -// }) + // Even though we synced the groups, we need to sync the group itself to see the new member + numMembers = (await boGroups[0].memberInboxIds()).length + assert(numMembers === 2, 'num members should be 2') -// test('sync function behaves as expected', async () => { -// const [alix, bo, caro] = await createClients(3) -// const alixGroup = await alix.conversations.newGroup([bo.address]) + await boGroups[0].sync() -// await alixGroup.send({ text: 'hello' }) + numMembers = (await boGroups[0].memberInboxIds()).length + assert(numMembers === 3, 'num members should be 3') -// // List groups will return empty until the first sync -// let boGroups = await bo.conversations.listGroups() -// assert(boGroups.length === 0, 'num groups for bo is 0 until we sync') + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const _alixGroup2 = await alix.conversations.newGroup([ + bo.address, + caro.address, + ]) + await bo.conversations.sync() + boGroups = await bo.conversations.listGroups() + assert(boGroups.length === 2, 'num groups for bo is 2') -// await bo.conversations.sync() + // Even before syncing the group, sync will return the initial number of members + numMembers = (await boGroups[1].memberInboxIds()).length + assert(numMembers === 3, 'num members should be 3') -// boGroups = await bo.conversations.listGroups() -// assert(boGroups.length === 1, 'num groups for bo is 1') + return true +}) -// // Num members will include the initial num of members even before sync -// let numMembers = (await boGroups[0].memberInboxIds()).length -// assert(numMembers === 2, 'num members should be 2') +test('can read and update group name', async () => { + const [alix, bo, caro] = await createClients(3) + const alixGroup = await alix.conversations.newGroup([bo.address]) -// // Num messages for a group will be 0 until we sync the group -// let numMessages = (await boGroups[0].messages()).length -// assert(numMessages === 0, 'num members should be 1') + await alixGroup.sync() + let groupName = await alixGroup.groupName() -// await bo.conversations.sync() + assert(groupName === '', 'group name should be empty string') -// // Num messages is still 0 because we didnt sync the group itself -// numMessages = (await boGroups[0].messages()).length -// assert(numMessages === 0, 'num messages should be 0') + await alixGroup.updateGroupName('Test name update 1') -// await boGroups[0].sync() + await alixGroup.sync() + groupName = await alixGroup.groupName() -// // after syncing the group we now see the correct number of messages -// numMessages = (await boGroups[0].messages()).length -// assert(numMessages === 1, 'num members should be 1') + assert( + groupName === 'Test name update 1', + 'group name should be "Test name update 1"' + ) -// await alixGroup.addMembers([caro.address]) + await bo.conversations.sync() + const boGroup = (await bo.conversations.listGroups())[0] + groupName = await boGroup.groupName() -// numMembers = (await boGroups[0].memberInboxIds()).length -// assert(numMembers === 2, 'num members should be 2') + assert(groupName === '', 'group name should be empty string') -// await bo.conversations.sync() + await boGroup.sync() -// // Even though we synced the groups, we need to sync the group itself to see the new member -// numMembers = (await boGroups[0].memberInboxIds()).length -// assert(numMembers === 2, 'num members should be 2') + groupName = await boGroup.groupName() -// await boGroups[0].sync() + assert( + groupName === 'Test name update 1', + 'group name should be "Test name update 1"' + ) -// numMembers = (await boGroups[0].memberInboxIds()).length -// assert(numMembers === 3, 'num members should be 3') + await alixGroup.addMembers([caro.address]) + await caro.conversations.sync() + const caroGroup = (await caro.conversations.listGroups())[0] -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// const _alixGroup2 = await alix.conversations.newGroup([ -// bo.address, -// caro.address, -// ]) -// await bo.conversations.sync() -// boGroups = await bo.conversations.listGroups() -// assert(boGroups.length === 2, 'num groups for bo is 2') + await caroGroup.sync() + groupName = await caroGroup.groupName() + assert( + groupName === 'Test name update 1', + 'group name should be "Test name update 1"' + ) + return true +}) -// // Even before syncing the group, sync will return the initial number of members -// numMembers = (await boGroups[1].memberInboxIds()).length -// assert(numMembers === 3, 'num members should be 3') +test('can list groups does not fork', async () => { + const [alix, bo] = await createClients(2) + console.log('created clients') + let groupCallbacks = 0 + //#region Stream groups + await bo.conversations.stream(async () => { + console.log('group received') + groupCallbacks++ + }) + //#region Stream All Messages + await bo.conversations.streamAllMessages(async () => { + console.log('message received') + }) + //#endregion + // #region create group + const alixGroup = await alix.conversations.newGroup([bo.address]) + await alixGroup.updateGroupName('hello') + await alixGroup.send('hello1') + console.log('sent group message') + // #endregion + // #region sync groups + await bo.conversations.sync() + // #endregion + const boGroups = await bo.conversations.listGroups() + assert(boGroups.length === 1, 'bo should have 1 group') + const boGroup = boGroups[0] + await boGroup.sync() -// return true -// }) + const boMessages1 = await boGroup.messages() + assert( + boMessages1.length === 2, + `should have 2 messages on first load received ${boMessages1.length}` + ) + await boGroup.send('hello2') + await boGroup.send('hello3') + await alixGroup.sync() + const alixMessages = await alixGroup.messages() + for (const message of alixMessages) { + console.log( + 'message', + message.contentTypeId, + message.contentTypeId === 'xmtp.org/text:1.0' + ? message.content() + : 'Group Updated' + ) + } + // alix sees 3 messages + assert( + alixMessages.length === 5, + `should have 5 messages on first load received ${alixMessages.length}` + ) + await alixGroup.send('hello4') + await boGroup.sync() + const boMessages2 = await boGroup.messages() + for (const message of boMessages2) { + console.log( + 'message', + message.contentTypeId, + message.contentTypeId === 'xmtp.org/text:1.0' + ? message.content() + : 'Group Updated' + ) + } + // bo sees 4 messages + assert( + boMessages2.length === 5, + `should have 5 messages on second load received ${boMessages2.length}` + ) -// test('can read and update group name', async () => { -// const [alix, bo, caro] = await createClients(3) -// const alixGroup = await alix.conversations.newGroup([bo.address]) + await delayToPropogate(500) -// await alixGroup.sync() -// let groupName = await alixGroup.groupName() + assert(groupCallbacks === 1, 'group stream should have received 1 group') -// assert(groupName === '', 'group name should be empty string') + return true +}) -// await alixGroup.updateGroupName('Test name update 1') +test('can list many groups members in parallel', async () => { + const [alix, bo] = await createClients(2) + const groups: Group[] = await createGroups(alix, [bo], 20) -// await alixGroup.sync() -// groupName = await alixGroup.groupName() + try { + await Promise.all(groups.slice(0, 10).map((g) => g.members())) + } catch (e) { + throw new Error(`Failed listing 10 groups members with ${e}`) + } -// assert( -// groupName === 'Test name update 1', -// 'group name should be "Test name update 1"' -// ) + try { + await Promise.all(groups.slice(0, 20).map((g) => g.members())) + } catch (e) { + throw new Error(`Failed listing 20 groups members with ${e}`) + } -// await bo.conversations.sync() -// const boGroup = (await bo.conversations.listGroups())[0] -// groupName = await boGroup.groupName() + return true +}) -// assert(groupName === '', 'group name should be empty string') +test('can sync all groups', async () => { + const [alix, bo] = await createClients(2) + const groups: Group[] = await createGroups(alix, [bo], 50) + + const alixGroup = groups[0] + await bo.conversations.sync() + const boGroup = await bo.conversations.findGroup(alixGroup.id) + await alixGroup.send('hi') + assert( + (await boGroup?.messages())?.length === 0, + `messages should be empty before sync but was ${boGroup?.messages?.length}` + ) -// await boGroup.sync() + const numGroupsSynced = await bo.conversations.syncAllConversations() + assert( + (await boGroup?.messages())?.length === 1, + `messages should be 4 after sync but was ${boGroup?.messages?.length}` + ) + assert( + numGroupsSynced === 51, + `should have synced 51 groups but synced ${numGroupsSynced}` + ) -// groupName = await boGroup.groupName() + for (const group of groups) { + await group.removeMembers([bo.address]) + } -// assert( -// groupName === 'Test name update 1', -// 'group name should be "Test name update 1"' -// ) + // First syncAllConversations after removal will still sync each group to set group inactive + const numGroupsSynced2 = await bo.conversations.syncAllConversations() + assert( + numGroupsSynced2 === 51, + `should have synced 51 groups but synced ${numGroupsSynced2}` + ) -// await alixGroup.addMembers([caro.address]) -// await caro.conversations.sync() -// const caroGroup = (await caro.conversations.listGroups())[0] + // Next syncAllConversations will not sync inactive groups + const numGroupsSynced3 = await bo.conversations.syncAllConversations() + assert( + numGroupsSynced3 === 1, + `should have synced 1 groups but synced ${numGroupsSynced3}` + ) + return true +}) -// await caroGroup.sync() -// groupName = await caroGroup.groupName() -// assert( -// groupName === 'Test name update 1', -// 'group name should be "Test name update 1"' -// ) -// return true -// }) +test('only streams groups that can be decrypted', async () => { + const [alixClient, boClient, caroClient] = await createClients(3) + const alixGroups: Conversation[] = [] + const boGroups: Conversation[] = [] + const caroGroups: Conversation[] = [] -// test('can list groups does not fork', async () => { -// const [alix, bo] = await createClients(2) -// console.log('created clients') -// let groupCallbacks = 0 -// //#region Stream groups -// await bo.conversations.stream(async () => { -// console.log('group received') -// groupCallbacks++ -// }) -// //#region Stream All Messages -// await bo.conversations.streamAllMessages(async () => { -// console.log('message received') -// }) -// //#endregion -// // #region create group -// const alixGroup = await alix.conversations.newGroup([bo.address]) -// await alixGroup.updateGroupName('hello') -// await alixGroup.send('hello1') -// console.log('sent group message') -// // #endregion -// // #region sync groups -// await bo.conversations.sync() -// // #endregion -// const boGroups = await bo.conversations.listGroups() -// assert(boGroups.length === 1, 'bo should have 1 group') -// const boGroup = boGroups[0] -// await boGroup.sync() - -// const boMessages1 = await boGroup.messages() -// assert( -// boMessages1.length === 2, -// `should have 2 messages on first load received ${boMessages1.length}` -// ) -// await boGroup.send('hello2') -// await boGroup.send('hello3') -// await alixGroup.sync() -// const alixMessages = await alixGroup.messages() -// for (const message of alixMessages) { -// console.log( -// 'message', -// message.contentTypeId, -// message.contentTypeId === 'xmtp.org/text:1.0' -// ? message.content() -// : 'Group Updated' -// ) -// } -// // alix sees 3 messages -// assert( -// alixMessages.length === 5, -// `should have 5 messages on first load received ${alixMessages.length}` -// ) -// await alixGroup.send('hello4') -// await boGroup.sync() -// const boMessages2 = await boGroup.messages() -// for (const message of boMessages2) { -// console.log( -// 'message', -// message.contentTypeId, -// message.contentTypeId === 'xmtp.org/text:1.0' -// ? message.content() -// : 'Group Updated' -// ) -// } -// // bo sees 4 messages -// assert( -// boMessages2.length === 5, -// `should have 5 messages on second load received ${boMessages2.length}` -// ) + await alixClient.conversations.stream(async (group: Conversation) => { + alixGroups.push(group) + }) + await boClient.conversations.stream(async (group: Conversation) => { + boGroups.push(group) + }) + await caroClient.conversations.stream(async (group: Conversation) => { + caroGroups.push(group) + }) -// await delayToPropogate(500) + await alixClient.conversations.newGroup([boClient.address]) + await delayToPropogate() + assert( + alixGroups.length === 1, + `alix group length should be 1 but was ${alixGroups.length}` + ) -// assert(groupCallbacks === 1, 'group stream should have received 1 group') + assert( + boGroups.length === 1, + `bo group length should be 1 but was ${boGroups.length}` + ) -// return true -// }) + assert( + caroGroups.length !== 1, + `caro group length should be 0 but was ${caroGroups.length}` + ) -// test('can list many groups members in parallel', async () => { -// const [alix, bo] = await createClients(2) -// const groups: Group[] = await createGroups(alix, [bo], 20) + return true +}) -// try { -// await Promise.all(groups.slice(0, 10).map((g) => g.members())) -// } catch (e) { -// throw new Error(`Failed listing 10 groups members with ${e}`) -// } +test('can stream groups and messages', async () => { + for (let index = 0; index < 15; index++) { + console.log(`stream groups & messages: test ${index}`) + const [alixClient, boClient] = await createClients(2) + + // Start streaming groups + const groups: Conversation[] = [] + await alixClient.conversations.stream(async (group: Conversation) => { + groups.push(group) + }) + // Stream messages twice + await alixClient.conversations.streamAllMessages(async (message) => {}) + await alixClient.conversations.streamAllMessages(async (message) => {}) + + // bo creates a group with alix so a stream callback is fired + // eslint-disable-next-line @typescript-eslint/no-unused-vars + await boClient.conversations.newGroup([alixClient.address]) + await delayToPropogate(500) + if ((groups.length as number) !== 1) { + throw Error(`Unexpected num groups (should be 1): ${groups.length}`) + } + } -// try { -// await Promise.all(groups.slice(0, 20).map((g) => g.members())) -// } catch (e) { -// throw new Error(`Failed listing 20 groups members with ${e}`) -// } + return true +}) -// return true -// }) +test('can create new installation without breaking group', async () => { + 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, + ]) + const wallet1 = Wallet.createRandom() + const wallet2 = Wallet.createRandom() + + const client1 = await Client.create(adaptEthersWalletToSigner(wallet1), { + env: 'local', + appVersion: 'Testing/0.0.0', + dbEncryptionKey: keyBytes, + }) + const client2 = await Client.create(adaptEthersWalletToSigner(wallet2), { + env: 'local', + appVersion: 'Testing/0.0.0', + dbEncryptionKey: keyBytes, + }) -// test('can sync all groups', async () => { -// const [alix, bo] = await createClients(2) -// const groups: Group[] = await createGroups(alix, [bo], 50) - -// const alixGroup = groups[0] -// await bo.conversations.sync() -// const boGroup = await bo.conversations.findGroup(alixGroup.id) -// await alixGroup.send('hi') -// assert( -// (await boGroup?.messages())?.length === 0, -// `messages should be empty before sync but was ${boGroup?.messages?.length}` -// ) - -// const numGroupsSynced = await bo.conversations.syncAllConversations() -// assert( -// (await boGroup?.messages())?.length === 1, -// `messages should be 4 after sync but was ${boGroup?.messages?.length}` -// ) -// assert( -// numGroupsSynced === 51, -// `should have synced 51 groups but synced ${numGroupsSynced}` -// ) - -// for (const group of groups) { -// await group.removeMembers([bo.address]) -// } + const group = await client1.conversations.newGroup([wallet2.address]) -// // First syncAllConversations after removal will still sync each group to set group inactive -// const numGroupsSynced2 = await bo.conversations.syncAllConversations() -// assert( -// numGroupsSynced2 === 51, -// `should have synced 51 groups but synced ${numGroupsSynced2}` -// ) - -// // Next syncAllConversations will not sync inactive groups -// const numGroupsSynced3 = await bo.conversations.syncAllConversations() -// assert( -// numGroupsSynced3 === 1, -// `should have synced 1 groups but synced ${numGroupsSynced3}` -// ) -// return true -// }) + await client1.conversations.sync() + await client2.conversations.sync() -// test('only streams groups that can be decrypted', async () => { -// const [alixClient, boClient, caroClient] = await createClients(3) -// const alixGroups: Conversation[] = [] -// const boGroups: Conversation[] = [] -// const caroGroups: Conversation[] = [] - -// await alixClient.conversations.stream(async (group: Conversation) => { -// alixGroups.push(group) -// }) -// await boClient.conversations.stream(async (group: Conversation) => { -// boGroups.push(group) -// }) -// await caroClient.conversations.stream(async (group: Conversation) => { -// caroGroups.push(group) -// }) - -// await alixClient.conversations.newGroup([boClient.address]) -// await delayToPropogate() -// assert( -// alixGroups.length === 1, -// `alix group length should be 1 but was ${alixGroups.length}` -// ) + const client1Group = await client1.conversations.findGroup(group.id) + const client2Group = await client2.conversations.findGroup(group.id) -// assert( -// boGroups.length === 1, -// `bo group length should be 1 but was ${boGroups.length}` -// ) + await client1Group?.sync() + await client2Group?.sync() -// assert( -// caroGroups.length !== 1, -// `caro group length should be 0 but was ${caroGroups.length}` -// ) + const members1 = await client1Group?.members() + assert( + members1?.length === 2, + `client 1 should see 2 members but was ${members1?.length}` + ) -// return true -// }) + const members2 = await client2Group?.members() + assert( + members2?.length === 2, + `client 2 should see 2 members but was ${members2?.length}` + ) -// test('can stream groups and messages', async () => { -// for (let index = 0; index < 15; index++) { -// console.log(`stream groups & messages: test ${index}`) -// const [alixClient, boClient] = await createClients(2) - -// // Start streaming groups -// const groups: Conversation[] = [] -// await alixClient.conversations.stream(async (group: Conversation) => { -// groups.push(group) -// }) -// // Stream messages twice -// await alixClient.conversations.streamAllMessages(async (message) => {}) -// await alixClient.conversations.streamAllMessages(async (message) => {}) - -// // bo creates a group with alix so a stream callback is fired -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// await boClient.conversations.newGroup([alixClient.address]) -// await delayToPropogate(500) -// if ((groups.length as number) !== 1) { -// throw Error(`Unexpected num groups (should be 1): ${groups.length}`) -// } -// } + await client2.deleteLocalDatabase() -// return true -// }) + // Recreating a client with wallet 2 (new installation!) + await Client.create(adaptEthersWalletToSigner(wallet2), { + env: 'local', + appVersion: 'Testing/0.0.0', + dbEncryptionKey: keyBytes, + }) -// test('can create new installation without breaking group', async () => { -// 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, -// ]) -// const wallet1 = Wallet.createRandom() -// const wallet2 = Wallet.createRandom() - -// const client1 = await Client.create(adaptEthersWalletToSigner(wallet1), { -// env: 'local', -// appVersion: 'Testing/0.0.0', -// dbEncryptionKey: keyBytes, -// }) -// const client2 = await Client.create(adaptEthersWalletToSigner(wallet2), { -// env: 'local', -// appVersion: 'Testing/0.0.0', -// dbEncryptionKey: keyBytes, -// }) - -// const group = await client1.conversations.newGroup([wallet2.address]) - -// await client1.conversations.sync() -// await client2.conversations.sync() - -// const client1Group = await client1.conversations.findGroup(group.id) -// const client2Group = await client2.conversations.findGroup(group.id) - -// await client1Group?.sync() -// await client2Group?.sync() - -// const members1 = await client1Group?.members() -// assert( -// members1?.length === 2, -// `client 1 should see 2 members but was ${members1?.length}` -// ) - -// const members2 = await client2Group?.members() -// assert( -// members2?.length === 2, -// `client 2 should see 2 members but was ${members2?.length}` -// ) - -// await client2.deleteLocalDatabase() - -// // Recreating a client with wallet 2 (new installation!) -// await Client.create(adaptEthersWalletToSigner(wallet2), { -// env: 'local', -// appVersion: 'Testing/0.0.0', -// dbEncryptionKey: keyBytes, -// }) - -// await client1Group?.send('This message will break the group') -// const members3 = await client1Group?.members() -// assert( -// members3?.length === 2, -// `client 1 should still see the 2 members but was ${members3?.length}` -// ) + await client1Group?.send('This message will break the group') + const members3 = await client1Group?.members() + assert( + members3?.length === 2, + `client 1 should still see the 2 members but was ${members3?.length}` + ) -// return true -// }) + return true +}) test('handles disappearing messages in a group', async () => { const [alixClient, boClient] = await createClients(2) @@ -2112,9 +2112,13 @@ test('handles disappearing messages in a group', async () => { // Disable disappearing messages await boGroup.clearDisappearingMessageSettings() + await delayToPropogate(1000) + await boGroup.sync() await alixGroup!.sync() + await delayToPropogate(1000) + // Validate disappearing messages are disabled await assertEqual( () => boGroup.disappearingMessageSettings(), @@ -2126,13 +2130,14 @@ test('handles disappearing messages in a group', async () => { undefined, 'AlixGroup should not have disappearing settings' ) + await assertEqual( - () => boGroup.isDisappearingMessagesEnabled, + () => boGroup.isDisappearingMessagesEnabled(), false, 'BoGroup should have disappearing disabled' ) await assertEqual( - () => alixGroup!.isDisappearingMessagesEnabled, + () => alixGroup!.isDisappearingMessagesEnabled(), false, 'AlixGroup should have disappearing disabled' ) @@ -2162,6 +2167,8 @@ test('handles disappearing messages in a group', async () => { retentionDurationInNs: 1_000_000_000, } await boGroup.updateDisappearingMessageSettings(updatedSettings) + await delayToPropogate(1000) + await boGroup.sync() await alixGroup!.sync() diff --git a/example/src/tests/test-utils.ts b/example/src/tests/test-utils.ts index 6d7c9121..378cd030 100644 --- a/example/src/tests/test-utils.ts +++ b/example/src/tests/test-utils.ts @@ -86,7 +86,8 @@ export function adaptEthersWalletToSigner(wallet: Wallet): Signer { } export async function assertEqual(actual: any, expected: any, message: string) { - const resolvedActual = typeof actual === 'function' ? await actual() : actual; + const resolvedActual = typeof actual === 'function' ? await actual() : actual + assert( resolvedActual === expected, `${message} Expected: ${expected}, but was: ${resolvedActual}` diff --git a/src/index.ts b/src/index.ts index 150a5432..fe26ed11 100644 --- a/src/index.ts +++ b/src/index.ts @@ -975,10 +975,14 @@ export async function disappearingMessageSettings( await XMTPModule.disappearingMessageSettings(installationId, conversationId) ) - return new DisappearingMessageSettings( - settings.disappearStartingAtNs, - settings.retentionDurationInNs - ) + if (!settings) { + return undefined + } else { + return new DisappearingMessageSettings( + settings.disappearStartingAtNs, + settings.retentionDurationInNs + ) + } } export async function isDisappearingMessagesEnabled( From 115dc752c1879d255d2dcab7313f3d6822b473e2 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 17 Feb 2025 18:16:57 -0800 Subject: [PATCH 19/20] bump the pod --- example/ios/Podfile.lock | 8 ++++---- ios/XMTPReactNative.podspec | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index a1039022..31c8f077 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -448,7 +448,7 @@ PODS: - SQLCipher/standard (4.5.7): - SQLCipher/common - SwiftProtobuf (1.28.2) - - XMTP (3.0.29): + - XMTP (3.0.30): - Connect-Swift (= 1.0.0) - CryptoSwift (= 1.8.3) - CSecp256k1 (~> 0.2) @@ -459,7 +459,7 @@ PODS: - ExpoModulesCore - MessagePacker - SQLCipher (= 4.5.7) - - XMTP (= 3.0.29) + - XMTP (= 3.0.30) - Yoga (1.14.0) DEPENDENCIES: @@ -762,8 +762,8 @@ SPEC CHECKSUMS: RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396 SQLCipher: 5e6bfb47323635c8b657b1b27d25c5f1baf63bf5 SwiftProtobuf: 4dbaffec76a39a8dc5da23b40af1a5dc01a4c02d - XMTP: 12cc7f309b993790a2a6c2a8bb2048c62fb4b788 - XMTPReactNative: 68152197c8135a6e0fe03637e7cde6bdd896d75c + XMTP: 49cba75672b7a8e1962acb5202d9e6c839fca984 + XMTPReactNative: 1b030fd3a857084edcf9055965bbc1d7cb77b4f6 Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9 PODFILE CHECKSUM: 2d04c11c2661aeaad852cd3ada0b0f1b06e0cf24 diff --git a/ios/XMTPReactNative.podspec b/ios/XMTPReactNative.podspec index b2d341e7..f86c0f77 100644 --- a/ios/XMTPReactNative.podspec +++ b/ios/XMTPReactNative.podspec @@ -26,7 +26,7 @@ Pod::Spec.new do |s| s.source_files = "**/*.{h,m,swift}" s.dependency "MessagePacker" - s.dependency "XMTP", "= 3.0.29" + s.dependency "XMTP", "= 3.0.30" s.dependency 'CSecp256k1', '~> 0.2' s.dependency "SQLCipher", "= 4.5.7" end From f044b101a8b32a78c6f1508acd23e78067642770 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 17 Feb 2025 20:04:35 -0800 Subject: [PATCH 20/20] pass on lint --- example/src/tests/conversationTests.ts | 4 ++-- example/src/tests/groupTests.ts | 2 +- src/lib/Client.ts | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/example/src/tests/conversationTests.ts b/example/src/tests/conversationTests.ts index f078bcc1..a06703d9 100644 --- a/example/src/tests/conversationTests.ts +++ b/example/src/tests/conversationTests.ts @@ -604,8 +604,8 @@ test('can list conversation messages', async () => { ) assert( - boDmMessages?.length === 2, - `alix conversation lengths should be 2 but was ${boDmMessages?.length}` + boDmMessages?.length === 3, + `alix conversation lengths should be 3 but was ${boDmMessages?.length}` ) return true diff --git a/example/src/tests/groupTests.ts b/example/src/tests/groupTests.ts index a6692bdf..0ad6906b 100644 --- a/example/src/tests/groupTests.ts +++ b/example/src/tests/groupTests.ts @@ -1,5 +1,6 @@ import { Wallet } from 'ethers' import { DefaultContentTypes } from 'xmtp-react-native-sdk/lib/types/DefaultContentType' +import { PermissionPolicySet } from 'xmtp-react-native-sdk/lib/types/PermissionPolicySet' import { Test, @@ -19,7 +20,6 @@ import { DecodedMessage, ConsentRecord, } from '../../../src/index' -import { PermissionPolicySet } from 'xmtp-react-native-sdk/lib/types/PermissionPolicySet' export const groupTests: Test[] = [] let counter = 1 diff --git a/src/lib/Client.ts b/src/lib/Client.ts index a5f5f17b..66fe9b8b 100644 --- a/src/lib/Client.ts +++ b/src/lib/Client.ts @@ -357,9 +357,12 @@ export class Client< * Add this account to the current inboxId. * Adding a wallet already associated with an inboxId will cause the wallet to lose access to that inbox. * @param {Signer} newAccount - The signer of the new account to be added. - * @param {boolean} allowReassignInboxId - The signer object used for authenticate the removal. + * @param {boolean} allowReassignInboxId - A boolean specifying if the inboxId should be reassigned or not. */ - async addAccount(newAccount: Signer | WalletClient, allowReassignInboxId: boolean = false) { + async addAccount( + newAccount: Signer | WalletClient, + allowReassignInboxId: boolean = false + ) { console.warn( '⚠️ This function is delicate and should be used with caution. ' + 'Adding a wallet already associated with an inboxId will cause the wallet to lose access to that inbox.'