From a25ebee6515648c34767bc3cf7f39bc8c447ea87 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 25 Apr 2025 14:56:22 -0700 Subject: [PATCH 1/2] try to write a test for it --- example/ios/Podfile.lock | 158 ++--- example/src/tests/contentTypeTests.ts | 846 ++++++++++++++------------ 2 files changed, 530 insertions(+), 474 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 49ae580e8..133e0c1a1 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -62,9 +62,9 @@ PODS: - hermes-engine/Pre-built (0.76.9) - LibXMTP (4.2.0-dev.4ba3b55) - MessagePacker (0.4.7) - - MMKV (2.1.0): - - MMKVCore (~> 2.1.0) - - MMKVCore (2.1.1) + - MMKV (2.2.1): + - MMKVCore (~> 2.2.1) + - MMKVCore (2.2.1) - OpenSSL-Universal (1.1.2200) - RCT-Folly (2024.10.14.00): - boost @@ -2062,19 +2062,19 @@ SPEC CHECKSUMS: CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483 CSecp256k1: 2a59c03e52637ded98896a33be4b2649392cb843 DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 - EXConstants: fcfc75800824ac2d5c592b5bc74130bad17b146b - EXImageLoader: e5da974e25b13585c196b658a440720c075482d5 - Expo: 75e002fc29a18a72aa3db967b41b29c2b206875d - ExpoAsset: 48386d40d53a8c1738929b3ed509bcad595b5516 - ExpoClipboard: 44fd1c8959ee8f6175d059dc011b154c9709a969 - ExpoCrypto: e97e864c8d7b9ce4a000bca45dddb93544a1b2b4 - ExpoDocumentPicker: 6d3d499cf15b692688a804f42927d0f35de5ebaa - ExpoFileSystem: 42d363d3b96f9afab980dcef60d5657a4443c655 - ExpoFont: f354e926f8feae5e831ec8087f36652b44a0b188 - ExpoImagePicker: 24e5ba8da111f74519b1e6dc556e0b438b2b8464 - ExpoKeepAwake: b0171a73665bfcefcfcc311742a72a956e6aa680 - ExpoModulesCore: 725faec070d590810d2ea5983d9f78f7cf6a38ec - ExpoSplashScreen: cb4e3d3ee646ed59810f7776cca0ae5c03ab4285 + EXConstants: a1f35b9aabbb3c6791f8e67722579b1ffcdd3f18 + EXImageLoader: 759063a65ab016b836f73972d3bb25404888713d + Expo: 83a0ca93325155885341b6cfc988c9c711dd0048 + ExpoAsset: 0687fe05f5d051c4a34dd1f9440bd00858413cfe + ExpoClipboard: 5250b207b6d545f4e9aac5ea3c6e61c4f16d0aed + ExpoCrypto: 1eaf79360c8135af1f2ebb133394fd3513ca9a3d + ExpoDocumentPicker: 8c1f88c2809ab2287350e8fac65964bf423578be + ExpoFileSystem: c8c19bf80d914c83dda3beb8569d7fb603be0970 + ExpoFont: 773955186469acc5108ff569712a2d243857475f + ExpoImagePicker: 482b2a6198b365dd18b5a0cb6d4caeec880cb8e1 + ExpoKeepAwake: 2a5f15dd4964cba8002c9a36676319a3394c85c7 + ExpoModulesCore: c2eeb11b2fc321dfc21b892be14c124dcac0a1e8 + ExpoSplashScreen: 48a5b55b370aaf48e8be0c7846c487e583067164 fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 7605ea4810e0e10ae4815292433c09bf4324ba45 fmt: 01b82d4ca6470831d1cc0852a1af644be019e8f6 @@ -2082,88 +2082,88 @@ SPEC CHECKSUMS: hermes-engine: 9e868dc7be781364296d6ee2f56d0c1a9ef0bb11 LibXMTP: 80e594671ce3f19f58fa4068403f98d5629d5111 MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02 - MMKV: ce484c1ac40bf76d5f09a0195d2ec5b3d3840d55 - MMKVCore: 1eb661c6c498ab88e3df9ce5d8ff94d05fcc0567 + MMKV: 383ccfa10ae23ff2a9c0f6130b7136b37b55ac10 + MMKVCore: b0e4c420da85636d7adaa535a53dfade59a22f5c OpenSSL-Universal: 6e1ae0555546e604dbc632a2b9a24a9c46c41ef6 - RCT-Folly: ea9d9256ba7f9322ef911169a9f696e5857b9e17 + RCT-Folly: 7b4f73a92ad9571b9dbdb05bb30fad927fa971e1 RCTDeprecation: ebe712bb05077934b16c6bf25228bdec34b64f83 RCTRequired: ca91e5dd26b64f577b528044c962baf171c6b716 RCTTypeSafety: e7678bd60850ca5a41df9b8dc7154638cb66871f React: 4641770499c39f45d4e7cde1eba30e081f9d8a3d React-callinvoker: 4bef67b5c7f3f68db5929ab6a4d44b8a002998ea - React-Core: a68cea3e762814e60ecc3fa521c7f14c36c99245 - React-CoreModules: d81b1eaf8066add66299bab9d23c9f00c9484c7c - React-cxxreact: 984f8b1feeca37181d4e95301fcd6f5f6501c6ab + React-Core: 0a06707a0b34982efc4a556aff5dae4b22863455 + React-CoreModules: 907334e94314189c2e5eed4877f3efe7b26d85b0 + React-cxxreact: 3a1d5e8f4faa5e09be26614e9c8bbcae8d11b73d React-debug: 817160c07dc8d24d020fbd1eac7b3558ffc08964 - React-defaultsnativemodule: 21f216e8db975897eb32b5f13247f5bbfaa97f41 - React-domnativemodule: 19270ad4b8d33312838d257f24731a0026809d49 - React-Fabric: f6dade7007533daeb785ba5925039d83f343be4b - React-FabricComponents: b0655cc3e1b5ae12a4a1119aa7d8308f0ad33520 - React-FabricImage: 9b157c4c01ac2bf433f834f0e1e5fe234113a576 + React-defaultsnativemodule: a965cb39fb0a79276ab611793d39f52e59a9a851 + React-domnativemodule: d647f94e503c62c44f54291334b1aa22a30fa08b + React-Fabric: 64586dc191fc1c170372a638b8e722e4f1d0a09b + React-FabricComponents: b0ebd032387468ea700574c581b139f57a7497fb + React-FabricImage: 81f0e0794caf25ad1224fa406d288fbc1986607f React-featureflags: f2792b067a351d86fdc7bec23db3b9a2f2c8d26c - React-featureflagsnativemodule: 3a8731d8fd9f755be57e00d9fa8a7f92aa77e87d - React-graphics: 68969e4e49d73f89da7abef4116c9b5f466aa121 - React-hermes: ac0bcba26a5d288ebc99b500e1097da2d0297ddf - React-idlecallbacksnativemodule: 9a2c5b5c174c0c476f039bedc1b9497a8272133e - React-ImageManager: e906eec93a9eb6102a06576b89d48d80a4683020 - React-jserrorhandler: ac5dde01104ff444e043cad8f574ca02756e20d6 - React-jsi: 496fa2b9d63b726aeb07d0ac800064617d71211d - React-jsiexecutor: dd22ab48371b80f37a0a30d0e8915b6d0f43a893 - React-jsinspector: 4629ac376f5765e684d19064f2093e55c97fd086 - React-jsitracing: 7a1c9cd484248870cf660733cd3b8114d54c035f - React-logger: c4052eb941cca9a097ef01b59543a656dc088559 - React-Mapbuffer: 33546a3ebefbccb8770c33a1f8a5554fa96a54de - React-microtasksnativemodule: 5c3d795318c22ab8df55100e50b151384a4a60b3 - react-native-blob-util: f7234c91ad0e3faeee51b3edee80b61553f74993 - react-native-config: 644074ab88db883fcfaa584f03520ec29589d7df - react-native-encrypted-storage: 569d114e329b1c2c2d9f8c84bcdbe4478dda2258 - react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba - react-native-mmkv: f0574e88f254d13d1a87cf6d38c36bc5d3910d49 - react-native-netinfo: be701059f57093572e5ba08cba14483d334b425d - react-native-quick-base64: 5565249122493bef017004646d73f918e8c2dfb0 - react-native-quick-crypto: c168ffba24470d8edfd03961d9492638431b9869 - react-native-randombytes: 3c8f3e89d12487fd03a2f966c288d495415fc116 - react-native-safe-area-context: fdb0a66feac038cb6eb1edafcf2ccee2b5cf0284 - react-native-sqlite-storage: 0c84826214baaa498796c7e46a5ccc9a82e114ed - react-native-webview: 5bb1454f1eb43e0bad229bb428a378d6b865a0ad + React-featureflagsnativemodule: 95a02d895475de8ace78fedd76143866838bb720 + React-graphics: cbebe910e4a15b65b0bff94a4d3ed278894d6386 + React-hermes: ec18c10f5a69d49fb9b5e17ae95494e9ea13d4d3 + React-idlecallbacksnativemodule: 0c1ae840cc5587197cd926a3cb76828ad059d116 + React-ImageManager: f2a4c01c2ccb2193e60a20c135da74c7ca4d36f2 + React-jserrorhandler: 61d205b5a7cbc57fed3371dd7eed48c97f49fc64 + React-jsi: 95f7676103137861b79b0f319467627bcfa629ee + React-jsiexecutor: 41e0fe87cda9ea3970ffb872ef10f1ff8dbd1932 + React-jsinspector: 15578208796723e5c6f39069b6e8bf36863ef6e2 + React-jsitracing: 3758cdb155ea7711f0e77952572ea62d90c69f0b + React-logger: dbca7bdfd4aa5ef69431362bde6b36d49403cb20 + React-Mapbuffer: 6efad4a606c1fae7e4a93385ee096681ef0300dc + React-microtasksnativemodule: 8732b71aa66045da4bb341ddee1bb539f71e5f38 + react-native-blob-util: 39a20f2ef11556d958dc4beb0aa07d1ef2690745 + react-native-config: 3367df9c1f25bb96197007ec531c7087ed4554c3 + react-native-encrypted-storage: db300a3f2f0aba1e818417c1c0a6be549038deb7 + react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06 + react-native-mmkv: e842cad766fc2ad46e70e161f4bbaf0b7e90d41d + react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983 + react-native-quick-base64: 764b8014da7dc834e5b8ad756c980addf919c177 + react-native-quick-crypto: 4a5011fb16940bf07059cb6d3f3388da95d77813 + react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846 + react-native-safe-area-context: f6fd1adb0c0dfb0a528f561c7c6f9297f3391af3 + react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261 + react-native-webview: 994b9f8fbb504d6314dc40d83f94f27c6831b3bf React-nativeconfig: 8efdb1ef1e9158c77098a93085438f7e7b463678 - React-NativeModulesApple: cebca2e5320a3d66e123cade23bd90a167ffce5e - React-perflogger: 72e653eb3aba9122f9e57cf012d22d2486f33358 - React-performancetimeline: cd6a9374a72001165995d2ab632f672df04076dc + React-NativeModulesApple: 958d4f6c5c2ace4c0f427cf7ef82e28ae6538a22 + React-perflogger: 9b4f13c0afe56bc7b4a0e93ec74b1150421ee22d + React-performancetimeline: 359db1cb889aa0282fafc5838331b0987c4915a9 React-RCTActionSheet: aacf2375084dea6e7c221f4a727e579f732ff342 - React-RCTAnimation: 395ab53fd064dff81507c15efb781c8684d9a585 - React-RCTAppDelegate: 1e5b43833e3e36e9fa34eec20be98174bc0e14a2 - React-RCTBlob: 13311e554c1a367de063c10ee7c5e6573b2dd1d6 - React-RCTFabric: bd906861a4e971e21d8df496c2d8f3ca6956f840 - React-RCTImage: 1b1f914bcc12187c49ba5d949dac38c2eb9f5cc8 - React-RCTLinking: 4ac7c42beb65e36fba0376f3498f3cd8dd0be7fa - React-RCTNetwork: 938902773add4381e84426a7aa17a2414f5f94f7 - React-RCTSettings: e848f1ba17a7a18479cf5a31d28145f567da8223 - React-RCTText: 7e98fafdde7d29e888b80f0b35544e0cb07913cf - React-RCTVibration: cd7d80affd97dc7afa62f9acd491419558b64b78 + React-RCTAnimation: d8c82deebebe3aaf7a843affac1b57cb2dc073d4 + React-RCTAppDelegate: 6c0377d9c4058773ea7073bb34bb9ebd6ddf5a84 + React-RCTBlob: 70a58c11a6a3500d1a12f2e51ca4f6c99babcff8 + React-RCTFabric: 7eb6dd2c8fda98cb860a572e3f4e4eb60d62c89e + React-RCTImage: 5e9d655ba6a790c31e3176016f9b47fd0978fbf0 + React-RCTLinking: 2a48338252805091f7521eaf92687206401bdf2a + React-RCTNetwork: 0c1282b377257f6b1c81934f72d8a1d0c010e4c3 + React-RCTSettings: f757b679a74e5962be64ea08d7865a7debd67b40 + React-RCTText: e7d20c490b407d3b4a2daa48db4bcd8ec1032af2 + React-RCTVibration: 8228e37144ca3122a91f1de16ba8e0707159cfec React-rendererconsistency: b4917053ecbaa91469c67a4319701c9dc0d40be6 - React-rendererdebug: aa181c36dd6cf5b35511d1ed875d6638fd38f0ec + React-rendererdebug: 81becbc8852b38d9b1b68672aa504556481330d5 React-rncore: 120d21715c9b4ba8f798bffe986cb769b988dd74 - React-RuntimeApple: d033becbbd1eba6f9f6e3af6f1893030ce203edd - React-RuntimeCore: 38af280bb678e66ba000a3c3d42920b2a138eebb + React-RuntimeApple: 52ed0e9e84a7c2607a901149fb13599a3c057655 + React-RuntimeCore: ca6189d2e53d86db826e2673fe8af6571b8be157 React-runtimeexecutor: 877596f82f5632d073e121cba2d2084b76a76899 - React-RuntimeHermes: 37aad735ff21ca6de2d8450a96de1afe9f86c385 - React-runtimescheduler: 8ec34cc885281a34696ea16c4fd86892d631f38d + React-RuntimeHermes: 3b752dc5d8a1661c9d1687391d6d96acfa385549 + React-runtimescheduler: 8321bb09175ace2a4f0b3e3834637eb85bf42ebe React-timing: 331cbf9f2668c67faddfd2e46bb7f41cbd9320b9 - React-utils: ed818f19ab445000d6b5c4efa9d462449326cc9f - ReactCodegen: f853a20cc9125c5521c8766b4b49375fec20648b - ReactCommon: 300d8d9c5cb1a6cd79a67cf5d8f91e4d477195f9 - RNCAsyncStorage: 357676e1dc19095208c80d4271066c407cd02ed1 - RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8 - RNScreens: 5cac36d8f7b3d92fb4304abcb44c5de336413df8 - RNSVG: a07e14363aa208062c6483bad24a438d5986d490 + React-utils: 54df9ada708578c8ad40d92895d6fed03e0e8a9e + ReactCodegen: 21a52ccddc6479448fc91903a437dd23ddc7366c + ReactCommon: bfd3600989d79bc3acbe7704161b171a1480b9fd + RNCAsyncStorage: 8616bd5a58af409453ea4e1b246521bb76578d60 + RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 + RNScreens: 17e17e8daec5b2c10e9d34f7c06731cc25b5195a + RNSVG: 76d7cafaaa603fbbcb05aff928653e720251f483 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 SQLCipher: 5e6bfb47323635c8b657b1b27d25c5f1baf63bf5 SwiftProtobuf: 4dbaffec76a39a8dc5da23b40af1a5dc01a4c02d XMTP: 71a89fc9b80df8edabc63fe2c63dbb7359fd0442 - XMTPReactNative: 46f5f600450d114d29eff2ca7e87c8a2ae4e937c + XMTPReactNative: d94f1f113d016f1b5b1f59ce1c874b4947fa6121 Yoga: feb4910aba9742cfedc059e2b2902e22ffe9954a PODFILE CHECKSUM: 283c313cbc1ba9857a692b5901eb740dad922eca -COCOAPODS: 1.16.2 +COCOAPODS: 1.15.2 diff --git a/example/src/tests/contentTypeTests.ts b/example/src/tests/contentTypeTests.ts index 91cbbe7dc..c7e26a328 100644 --- a/example/src/tests/contentTypeTests.ts +++ b/example/src/tests/contentTypeTests.ts @@ -19,426 +19,482 @@ function test(name: string, perform: () => Promise) { }) } -test('can fetch messages with reactions', async () => { +// test('can fetch messages with reactions', async () => { +// const [alix, bo] = await createClients(2) + +// // Create group and sync +// const group = await alix.conversations.newGroup([bo.inboxId]) +// await bo.conversations.sync() +// const boGroup = await bo.conversations.findGroup(group.id) + +// // Send 3 messages from alix +// await group.send('message 1') +// await group.send('message 2') +// await group.send('message 3') + +// await delayToPropogate() +// await boGroup?.sync() + +// // Get messages to react to +// const messages = await boGroup?.messages() +// assert(messages?.length === 3, 'Should have 3 messages') + +// // Bo sends reactions to first two messages +// await boGroup?.send({ +// reaction: { +// action: 'added', +// content: '👍', +// reference: messages![0].id, +// schema: 'unicode', +// }, +// }) + +// await boGroup?.send({ +// reaction: { +// action: 'added', +// content: '❤️', +// reference: messages![1].id, +// schema: 'unicode', +// }, +// }) + +// await delayToPropogate() +// await group.sync() + +// // Get regular messages +// const regularMessages = await group.messages() +// assert( +// regularMessages.length === 6, +// 'Should have 5 total messages including reactions, but got ' + +// regularMessages.length +// ) + +// // Get messages with reactions +// const messagesWithReactions = await group.messagesWithReactions() +// assert(messagesWithReactions.length === 4, 'Should have 4 original messages') + +// // Check reactions are attached to correct messages +// const firstMessage = messagesWithReactions[0] // Reverse chronological +// const secondMessage = messagesWithReactions[1] +// const thirdMessage = messagesWithReactions[2] + +// assert( +// firstMessage.childMessages?.length === 1, +// 'First message should have 1 reaction' +// ) +// let messageType = firstMessage.childMessages![0].contentTypeId +// assert( +// messageType === 'xmtp.org/reaction:1.0', +// 'First message should have reaction type, but got ' + messageType +// ) +// let messageContent: ReactionContent = +// firstMessage.childMessages![0].content() as ReactionContent +// assert( +// messageContent.content === '👍', +// 'First message should have thumbs up, but got ' + messageContent.content +// ) + +// assert( +// secondMessage.childMessages?.length === 1, +// 'Second message should have 1 reaction' +// ) +// messageType = secondMessage.childMessages![0].contentTypeId +// assert( +// messageType === 'xmtp.org/reaction:1.0', +// 'Second message should have reaction type, but got ' + messageType +// ) +// messageContent = secondMessage.childMessages![0].content() as ReactionContent +// assert( +// messageContent.content === '❤️', +// 'Second message should have heart, but got ' + messageContent.content +// ) + +// assert( +// !thirdMessage.childMessages?.length, +// 'Third message should have no reactions' +// ) + +// return true +// }) + +// test('can use reaction v2 from rust/proto', async () => { +// const [alix, bo] = await createClients(2) + +// // Create group and sync +// const group = await alix.conversations.newGroup([bo.inboxId]) +// await bo.conversations.sync() +// const boGroup = await bo.conversations.findGroup(group.id) + +// // Send 3 messages from alix +// await group.send('message 1') +// await group.send('message 2') +// await group.send('message 3') + +// await delayToPropogate() +// await boGroup?.sync() + +// // Get messages to react to +// const messages = await boGroup?.messages() +// assert(messages?.length === 3, 'Should have 3 messages') + +// // Bo sends reaction V2 to first two messages +// await boGroup?.send({ +// reactionV2: { +// action: 'added', +// content: '👍', +// reference: messages![0].id, +// schema: 'unicode', +// }, +// }) + +// await boGroup?.send({ +// reactionV2: { +// action: 'added', +// content: '❤️', +// reference: messages![1].id, +// schema: 'unicode', +// }, +// }) + +// await delayToPropogate() +// await group.sync() + +// // Get regular messages +// const regularMessages = await group.messages() +// assert( +// regularMessages.length === 6, +// 'Should have 6 total messages including reactions, but got ' + +// regularMessages.length +// ) + +// // Get messages with reactions +// const messagesWithReactions = await group.messagesWithReactions() +// assert(messagesWithReactions.length === 4, 'Should have 4 original messages') + +// // Check reactions are attached to correct messages +// const firstMessage = messagesWithReactions[0] // Reverse chronological +// const secondMessage = messagesWithReactions[1] +// const thirdMessage = messagesWithReactions[2] + +// assert( +// firstMessage.childMessages?.length === 1, +// 'First message should have 1 reaction' +// ) +// let messageType = firstMessage.childMessages![0].contentTypeId +// assert( +// messageType === 'xmtp.org/reaction:2.0', +// 'First message should have reaction V2 type, but got ' + messageType +// ) +// let messageContent: ReactionContent = +// firstMessage.childMessages![0].content() as ReactionContent +// assert( +// messageContent.content === '👍', +// 'First message should have thumbs up, but got ' + messageContent.content +// ) + +// assert( +// secondMessage.childMessages?.length === 1, +// 'Second message should have 1 reaction' +// ) +// messageType = secondMessage.childMessages![0].contentTypeId +// assert( +// messageType === 'xmtp.org/reaction:2.0', +// 'Second message should have reaction V2 type, but got ' + messageType +// ) +// messageContent = secondMessage.childMessages![0].content() as ReactionContent +// assert( +// messageContent.content === '❤️', +// 'Second message should have heart, but got ' + messageContent.content +// ) +// assert( +// messageContent.reference === messages![1].id, +// 'Second message should have reference to second message, but got ' + +// messageContent.reference +// ) +// assert( +// messageContent.action === 'added', +// 'Second message should have added action, but got ' + messageContent.action +// ) +// assert( +// messageContent.schema === 'unicode', +// 'Second message should have unicode schema, but got ' + +// messageContent.schema +// ) + +// assert( +// !thirdMessage.childMessages?.length, +// 'Third message should have no reactions' +// ) + +// return true +// }) + +// test('remote attachments should work', async () => { +// const [alix, bo] = await createClients(2) +// const convo = await alix.conversations.newConversation(bo.inboxId) + +// // Alice is sending Bob a file from her phone. +// const filename = `${Date.now()}.txt` +// const file = `${fs.dirs.CacheDir}/${filename}` +// await fs.writeFile(file, 'hello world', 'utf8') +// const { encryptedLocalFileUri, metadata } = await alix.encryptAttachment({ +// fileUri: `file://${file}`, +// mimeType: 'text/plain', +// }) + +// const encryptedFile = encryptedLocalFileUri.slice('file://'.length) +// const originalContent = await fs.readFile(file, 'base64') +// const encryptedContent = await fs.readFile(encryptedFile, 'base64') +// if (encryptedContent === originalContent) { +// throw new Error('encrypted file should not match original') +// } + +// // This is where the app will upload the encrypted file to a remote server and generate a URL. +// // let url = await uploadFile(encryptedLocalFileUri); +// const url = 'https://example.com/123' + +// // Together with the metadata, we send the URL as a remoteAttachment message to the conversation. +// await convo.send({ +// remoteAttachment: { +// ...metadata, +// scheme: 'https://', +// url, +// }, +// }) +// await delayToPropogate() + +// // Now we should see the remote attachment message. +// const messages = await convo.messages() +// if (messages.length !== 2) { +// throw new Error('Expected 1 message') +// } +// const message = messages[0] + +// if (message.contentTypeId !== 'xmtp.org/remoteStaticAttachment:1.0') { +// throw new Error('Expected correctly formatted typeId') +// } +// if (!message.content()) { +// throw new Error('Expected remoteAttachment') +// } +// if ( +// (message.content() as RemoteAttachmentContent).url !== +// 'https://example.com/123' +// ) { +// throw new Error('Expected url to match') +// } + +// // This is where the app prompts the user to download the encrypted file from `url`. +// // TODO: let downloadedFile = await downloadFile(url); +// // But to simplify this test, we're just going to copy +// // the previously encrypted file and pretend that we just downloaded it. +// const downloadedFileUri = `file://${fs.dirs.CacheDir}/${Date.now()}.bin` +// await fs.cp( +// new URL(encryptedLocalFileUri).pathname, +// new URL(downloadedFileUri).pathname +// ) + +// // Now we can decrypt the downloaded file using the message metadata. +// const attached = await alix.decryptAttachment({ +// encryptedLocalFileUri: downloadedFileUri, +// metadata: message.content() as RemoteAttachmentContent, +// }) +// if (attached.mimeType !== 'text/plain') { +// throw new Error('Expected mimeType to match') +// } +// if (attached.filename !== filename) { +// throw new Error(`Expected ${attached.filename} to equal ${filename}`) +// } +// const text = await fs.readFile(new URL(attached.fileUri).pathname, 'utf8') +// if (text !== 'hello world') { +// throw new Error('Expected text to match') +// } +// return true +// }) + +// const attachmentUrlMap: Map = new Map() + +// function testUploadAttachmentForUrl(uriLocalEncryptedData: string): string { +// const url = 'https://' + Math.random().toString(36).substring(2, 15) + '.com' +// attachmentUrlMap.set(url, uriLocalEncryptedData) +// return url +// } + +// function testDownloadFromUrlForLocalUri(url: string): string { +// const attachmentUriAfterDownload = attachmentUrlMap.get(url) +// if (!attachmentUriAfterDownload) { +// throw new Error('Expected attachment to exist') +// } +// return attachmentUriAfterDownload +// } + +// type fileInfo = { +// filename: string +// fileUri: string +// } + +// test('multi remote attachments should work', async () => { +// const [alix, bo] = await createClients(2) +// const convo = await alix.conversations.newConversation(bo.inboxId) + +// // Alice is sending Bob two files from her phone. +// const filename1 = `${Date.now()}.txt` +// const file1 = `${fs.dirs.CacheDir}/${filename1}` +// await fs.writeFile(file1, 'hello world 1', 'utf8') +// const fileInfo1: fileInfo = { +// filename: filename1, +// fileUri: `file://${file1}`, +// } + +// const filename2 = `${Date.now()}.txt` +// const file2 = `${fs.dirs.CacheDir}/${filename2}` +// await fs.writeFile(file2, 'hello world 2', 'utf8') +// const fileInfo2: fileInfo = { +// filename: filename2, +// fileUri: `file://${file2}`, +// } + +// const remoteAttachments: RemoteAttachmentInfo[] = [] +// for (const fileInfo of [fileInfo1, fileInfo2]) { +// const { encryptedLocalFileUri, metadata } = await alix.encryptAttachment({ +// fileUri: fileInfo.fileUri, +// mimeType: 'text/plain', +// filename: fileInfo.filename, +// }) +// console.log('encryptedLocalFileUri saved to: ', encryptedLocalFileUri) + +// const url = testUploadAttachmentForUrl(encryptedLocalFileUri) +// const remoteAttachmentInfo = +// MultiRemoteAttachmentCodec.buildMultiRemoteAttachmentInfo(url, metadata) +// remoteAttachments.push(remoteAttachmentInfo) +// } + +// await convo.send({ +// multiRemoteAttachment: { +// attachments: remoteAttachments, +// }, +// }) + +// await delayToPropogate() + +// // Now we should see the remote attachment message. +// const messages = await convo.messages() +// if (messages.length !== 2) { +// throw new Error('Expected 2 message') +// } +// const message = messages[0] + +// if (message.contentTypeId !== 'xmtp.org/multiRemoteStaticAttachment:1.0') { +// throw new Error('Expected correctly formatted typeId') +// } +// if (!message.content()) { +// throw new Error('Expected multiRemoteAttachment') +// } + +// const multiRemoteAttachment = +// message.content() as MultiRemoteAttachmentContent +// if (multiRemoteAttachment.attachments.length !== 2) { +// throw new Error('Expected 2 attachments') +// } + +// assert( +// multiRemoteAttachment.attachments[0].url === remoteAttachments[0].url, +// 'Expected url to match' +// ) +// assert( +// multiRemoteAttachment.attachments[1].url === remoteAttachments[1].url, +// 'Expected url to match' +// ) + +// // Show how when we can convert a multiRemoteAttachment back into decrypted encoded content + +// const files: string[] = [] +// for (const attachment of multiRemoteAttachment.attachments) { +// // Simulate downloading the encrypted payload from the URL and saving it locally +// const attachmentUriAfterDownload: string = testDownloadFromUrlForLocalUri( +// attachment.url +// ) +// // Decrypt the local file +// const decryptedLocalAttachment = await alix.decryptAttachment({ +// encryptedLocalFileUri: attachmentUriAfterDownload, +// metadata: { +// secret: attachment.secret, +// salt: attachment.salt, +// nonce: attachment.nonce, +// contentDigest: attachment.contentDigest, +// filename: attachment.filename, +// } as RemoteAttachmentContent, +// }) +// assert( +// decryptedLocalAttachment.fileUri.startsWith('file:/'), +// 'Expected fileUri to start with file:// but it is ' + +// decryptedLocalAttachment.fileUri +// ) +// // Read the decrypted file +// const text = await fs.readFile( +// new URL(decryptedLocalAttachment.fileUri).pathname, +// 'utf8' +// ) +// files.push(text) +// } + +// assert(files[0] === 'hello world 1', 'Expected text to match') +// assert(files[1] === 'hello world 2', 'Expected text to match') + +// return true +// }) + +test('can stream conversations and messages including reactions', async () => { const [alix, bo] = await createClients(2) - // Create group and sync + let messageCallbacks = 0 const group = await alix.conversations.newGroup([bo.inboxId]) - await bo.conversations.sync() - const boGroup = await bo.conversations.findGroup(group.id) + const dm = await alix.conversations.findOrCreateDm(bo.inboxId) - // Send 3 messages from alix - await group.send('message 1') - await group.send('message 2') - await group.send('message 3') + await bo.conversations.syncAllConversations() - await delayToPropogate() - await boGroup?.sync() - - // Get messages to react to - const messages = await boGroup?.messages() - assert(messages?.length === 3, 'Should have 3 messages') - - // Bo sends reactions to first two messages - await boGroup?.send({ - reaction: { - action: 'added', - content: '👍', - reference: messages![0].id, - schema: 'unicode', - }, - }) - - await boGroup?.send({ - reaction: { - action: 'added', - content: '❤️', - reference: messages![1].id, - schema: 'unicode', - }, - }) - - await delayToPropogate() - await group.sync() - - // Get regular messages - const regularMessages = await group.messages() - assert( - regularMessages.length === 6, - 'Should have 5 total messages including reactions, but got ' + - regularMessages.length - ) - - // Get messages with reactions - const messagesWithReactions = await group.messagesWithReactions() - assert(messagesWithReactions.length === 4, 'Should have 4 original messages') - - // Check reactions are attached to correct messages - const firstMessage = messagesWithReactions[0] // Reverse chronological - const secondMessage = messagesWithReactions[1] - const thirdMessage = messagesWithReactions[2] - - assert( - firstMessage.childMessages?.length === 1, - 'First message should have 1 reaction' - ) - let messageType = firstMessage.childMessages![0].contentTypeId assert( - messageType === 'xmtp.org/reaction:1.0', - 'First message should have reaction type, but got ' + messageType + (await bo.conversations.list()).length === 2, + `convrtdsyion length should be 2` ) - let messageContent: ReactionContent = - firstMessage.childMessages![0].content() as ReactionContent - assert( - messageContent.content === '👍', - 'First message should have thumbs up, but got ' + messageContent.content - ) - - assert( - secondMessage.childMessages?.length === 1, - 'Second message should have 1 reaction' - ) - messageType = secondMessage.childMessages![0].contentTypeId - assert( - messageType === 'xmtp.org/reaction:1.0', - 'Second message should have reaction type, but got ' + messageType - ) - messageContent = secondMessage.childMessages![0].content() as ReactionContent - assert( - messageContent.content === '❤️', - 'Second message should have heart, but got ' + messageContent.content - ) - - assert( - !thirdMessage.childMessages?.length, - 'Third message should have no reactions' - ) - - return true -}) - -test('can use reaction v2 from rust/proto', async () => { - const [alix, bo] = await createClients(2) - - // Create group and sync - const group = await alix.conversations.newGroup([bo.inboxId]) - await bo.conversations.sync() - const boGroup = await bo.conversations.findGroup(group.id) + // Start message stream + await bo.conversations.streamAllMessages(async (msg) => { + messageCallbacks++ + }) - // Send 3 messages from alix - await group.send('message 1') - await group.send('message 2') - await group.send('message 3') + // Send regular messages + const messageId1 = await group.send('hello group') + const messageId2 = await dm.send('hello dm') + await delayToPropogate() await delayToPropogate() - await boGroup?.sync() - // Get messages to react to - const messages = await boGroup?.messages() - assert(messages?.length === 3, 'Should have 3 messages') - // Bo sends reaction V2 to first two messages - await boGroup?.send({ + // Send reactions to those messages + await group.send({ reactionV2: { action: 'added', - content: '👍', - reference: messages![0].id, + content: '😃', // Reaction V2 + reference: messageId1, schema: 'unicode', }, }) - - await boGroup?.send({ - reactionV2: { + await dm.send({ + reaction: { action: 'added', - content: '❤️', - reference: messages![1].id, + content: '😄', // Reaction V1 + reference: messageId2, schema: 'unicode', }, }) - - await delayToPropogate() - await group.sync() - - // Get regular messages - const regularMessages = await group.messages() - assert( - regularMessages.length === 6, - 'Should have 6 total messages including reactions, but got ' + - regularMessages.length - ) - - // Get messages with reactions - const messagesWithReactions = await group.messagesWithReactions() - assert(messagesWithReactions.length === 4, 'Should have 4 original messages') - - // Check reactions are attached to correct messages - const firstMessage = messagesWithReactions[0] // Reverse chronological - const secondMessage = messagesWithReactions[1] - const thirdMessage = messagesWithReactions[2] - - assert( - firstMessage.childMessages?.length === 1, - 'First message should have 1 reaction' - ) - let messageType = firstMessage.childMessages![0].contentTypeId - assert( - messageType === 'xmtp.org/reaction:2.0', - 'First message should have reaction V2 type, but got ' + messageType - ) - let messageContent: ReactionContent = - firstMessage.childMessages![0].content() as ReactionContent - assert( - messageContent.content === '👍', - 'First message should have thumbs up, but got ' + messageContent.content - ) - - assert( - secondMessage.childMessages?.length === 1, - 'Second message should have 1 reaction' - ) - messageType = secondMessage.childMessages![0].contentTypeId - assert( - messageType === 'xmtp.org/reaction:2.0', - 'Second message should have reaction V2 type, but got ' + messageType - ) - messageContent = secondMessage.childMessages![0].content() as ReactionContent - assert( - messageContent.content === '❤️', - 'Second message should have heart, but got ' + messageContent.content - ) - assert( - messageContent.reference === messages![1].id, - 'Second message should have reference to second message, but got ' + - messageContent.reference - ) - assert( - messageContent.action === 'added', - 'Second message should have added action, but got ' + messageContent.action - ) - assert( - messageContent.schema === 'unicode', - 'Second message should have unicode schema, but got ' + - messageContent.schema - ) - - assert( - !thirdMessage.childMessages?.length, - 'Third message should have no reactions' - ) - - return true -}) - -test('remote attachments should work', async () => { - const [alix, bo] = await createClients(2) - const convo = await alix.conversations.newConversation(bo.inboxId) - - // Alice is sending Bob a file from her phone. - const filename = `${Date.now()}.txt` - const file = `${fs.dirs.CacheDir}/${filename}` - await fs.writeFile(file, 'hello world', 'utf8') - const { encryptedLocalFileUri, metadata } = await alix.encryptAttachment({ - fileUri: `file://${file}`, - mimeType: 'text/plain', - }) - - const encryptedFile = encryptedLocalFileUri.slice('file://'.length) - const originalContent = await fs.readFile(file, 'base64') - const encryptedContent = await fs.readFile(encryptedFile, 'base64') - if (encryptedContent === originalContent) { - throw new Error('encrypted file should not match original') - } - - // This is where the app will upload the encrypted file to a remote server and generate a URL. - // let url = await uploadFile(encryptedLocalFileUri); - const url = 'https://example.com/123' - - // Together with the metadata, we send the URL as a remoteAttachment message to the conversation. - await convo.send({ - remoteAttachment: { - ...metadata, - scheme: 'https://', - url, - }, - }) await delayToPropogate() - // Now we should see the remote attachment message. - const messages = await convo.messages() - if (messages.length !== 2) { - throw new Error('Expected 1 message') - } - const message = messages[0] - - if (message.contentTypeId !== 'xmtp.org/remoteStaticAttachment:1.0') { - throw new Error('Expected correctly formatted typeId') - } - if (!message.content()) { - throw new Error('Expected remoteAttachment') - } - if ( - (message.content() as RemoteAttachmentContent).url !== - 'https://example.com/123' - ) { - throw new Error('Expected url to match') - } - - // This is where the app prompts the user to download the encrypted file from `url`. - // TODO: let downloadedFile = await downloadFile(url); - // But to simplify this test, we're just going to copy - // the previously encrypted file and pretend that we just downloaded it. - const downloadedFileUri = `file://${fs.dirs.CacheDir}/${Date.now()}.bin` - await fs.cp( - new URL(encryptedLocalFileUri).pathname, - new URL(downloadedFileUri).pathname - ) - - // Now we can decrypt the downloaded file using the message metadata. - const attached = await alix.decryptAttachment({ - encryptedLocalFileUri: downloadedFileUri, - metadata: message.content() as RemoteAttachmentContent, - }) - if (attached.mimeType !== 'text/plain') { - throw new Error('Expected mimeType to match') - } - if (attached.filename !== filename) { - throw new Error(`Expected ${attached.filename} to equal ${filename}`) - } - const text = await fs.readFile(new URL(attached.fileUri).pathname, 'utf8') - if (text !== 'hello world') { - throw new Error('Expected text to match') - } - return true -}) - -const attachmentUrlMap: Map = new Map() - -function testUploadAttachmentForUrl(uriLocalEncryptedData: string): string { - const url = 'https://' + Math.random().toString(36).substring(2, 15) + '.com' - attachmentUrlMap.set(url, uriLocalEncryptedData) - return url -} - -function testDownloadFromUrlForLocalUri(url: string): string { - const attachmentUriAfterDownload = attachmentUrlMap.get(url) - if (!attachmentUriAfterDownload) { - throw new Error('Expected attachment to exist') - } - return attachmentUriAfterDownload -} - -type fileInfo = { - filename: string - fileUri: string -} - -test('multi remote attachments should work', async () => { - const [alix, bo] = await createClients(2) - const convo = await alix.conversations.newConversation(bo.inboxId) - - // Alice is sending Bob two files from her phone. - const filename1 = `${Date.now()}.txt` - const file1 = `${fs.dirs.CacheDir}/${filename1}` - await fs.writeFile(file1, 'hello world 1', 'utf8') - const fileInfo1: fileInfo = { - filename: filename1, - fileUri: `file://${file1}`, - } - - const filename2 = `${Date.now()}.txt` - const file2 = `${fs.dirs.CacheDir}/${filename2}` - await fs.writeFile(file2, 'hello world 2', 'utf8') - const fileInfo2: fileInfo = { - filename: filename2, - fileUri: `file://${file2}`, - } - - const remoteAttachments: RemoteAttachmentInfo[] = [] - for (const fileInfo of [fileInfo1, fileInfo2]) { - const { encryptedLocalFileUri, metadata } = await alix.encryptAttachment({ - fileUri: fileInfo.fileUri, - mimeType: 'text/plain', - filename: fileInfo.filename, - }) - console.log('encryptedLocalFileUri saved to: ', encryptedLocalFileUri) - - const url = testUploadAttachmentForUrl(encryptedLocalFileUri) - const remoteAttachmentInfo = - MultiRemoteAttachmentCodec.buildMultiRemoteAttachmentInfo(url, metadata) - remoteAttachments.push(remoteAttachmentInfo) - } - - await convo.send({ - multiRemoteAttachment: { - attachments: remoteAttachments, - }, - }) - - await delayToPropogate() - - // Now we should see the remote attachment message. - const messages = await convo.messages() - if (messages.length !== 2) { - throw new Error('Expected 2 message') - } - const message = messages[0] - - if (message.contentTypeId !== 'xmtp.org/multiRemoteStaticAttachment:1.0') { - throw new Error('Expected correctly formatted typeId') - } - if (!message.content()) { - throw new Error('Expected multiRemoteAttachment') - } - - const multiRemoteAttachment = - message.content() as MultiRemoteAttachmentContent - if (multiRemoteAttachment.attachments.length !== 2) { - throw new Error('Expected 2 attachments') - } - + // Validate message stream callbacks (4: 2 messages + 2 reactions) assert( - multiRemoteAttachment.attachments[0].url === remoteAttachments[0].url, - 'Expected url to match' + messageCallbacks === 4, + `message stream should have received 4 messages (including reactions), got ${messageCallbacks}` ) - assert( - multiRemoteAttachment.attachments[1].url === remoteAttachments[1].url, - 'Expected url to match' - ) - - // Show how when we can convert a multiRemoteAttachment back into decrypted encoded content - - const files: string[] = [] - for (const attachment of multiRemoteAttachment.attachments) { - // Simulate downloading the encrypted payload from the URL and saving it locally - const attachmentUriAfterDownload: string = testDownloadFromUrlForLocalUri( - attachment.url - ) - // Decrypt the local file - const decryptedLocalAttachment = await alix.decryptAttachment({ - encryptedLocalFileUri: attachmentUriAfterDownload, - metadata: { - secret: attachment.secret, - salt: attachment.salt, - nonce: attachment.nonce, - contentDigest: attachment.contentDigest, - filename: attachment.filename, - } as RemoteAttachmentContent, - }) - assert( - decryptedLocalAttachment.fileUri.startsWith('file:/'), - 'Expected fileUri to start with file:// but it is ' + - decryptedLocalAttachment.fileUri - ) - // Read the decrypted file - const text = await fs.readFile( - new URL(decryptedLocalAttachment.fileUri).pathname, - 'utf8' - ) - files.push(text) - } - - assert(files[0] === 'hello world 1', 'Expected text to match') - assert(files[1] === 'hello world 2', 'Expected text to match') + // Cancel the streams + bo.conversations.cancelStreamAllMessages() return true }) From 51d29f08f14593581c30321aa79a9d88d4f33927 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Tue, 29 Apr 2025 14:38:19 -0700 Subject: [PATCH 2/2] try testing --- example/src/tests/contentTypeTests.ts | 56 - example/src/tests/conversationTests.ts | 2192 ++++++++++++------------ 2 files changed, 1123 insertions(+), 1125 deletions(-) diff --git a/example/src/tests/contentTypeTests.ts b/example/src/tests/contentTypeTests.ts index c7e26a328..5397d2fa2 100644 --- a/example/src/tests/contentTypeTests.ts +++ b/example/src/tests/contentTypeTests.ts @@ -442,59 +442,3 @@ function test(name: string, perform: () => Promise) { // return true // }) - -test('can stream conversations and messages including reactions', async () => { - const [alix, bo] = await createClients(2) - - let messageCallbacks = 0 - const group = await alix.conversations.newGroup([bo.inboxId]) - const dm = await alix.conversations.findOrCreateDm(bo.inboxId) - - await bo.conversations.syncAllConversations() - - assert( - (await bo.conversations.list()).length === 2, - `convrtdsyion length should be 2` - ) - // Start message stream - await bo.conversations.streamAllMessages(async (msg) => { - messageCallbacks++ - }) - - - // Send regular messages - const messageId1 = await group.send('hello group') - const messageId2 = await dm.send('hello dm') - await delayToPropogate() - await delayToPropogate() - - - // Send reactions to those messages - await group.send({ - reactionV2: { - action: 'added', - content: '😃', // Reaction V2 - reference: messageId1, - schema: 'unicode', - }, - }) - await dm.send({ - reaction: { - action: 'added', - content: '😄', // Reaction V1 - reference: messageId2, - schema: 'unicode', - }, - }) - await delayToPropogate() - - // Validate message stream callbacks (4: 2 messages + 2 reactions) - assert( - messageCallbacks === 4, - `message stream should have received 4 messages (including reactions), got ${messageCallbacks}` - ) - - // Cancel the streams - bo.conversations.cancelStreamAllMessages() - return true -}) diff --git a/example/src/tests/conversationTests.ts b/example/src/tests/conversationTests.ts index 1b789181e..bc3714c51 100644 --- a/example/src/tests/conversationTests.ts +++ b/example/src/tests/conversationTests.ts @@ -102,517 +102,571 @@ class NumberCodecEmptyFallback extends NumberCodec { } } -test('register and use custom content types', 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 bo = await Client.createRandom({ - env: 'local', - codecs: [new NumberCodec()], - dbEncryptionKey: keyBytes, - }) - const alix = await Client.createRandom({ - env: 'local', - codecs: [new NumberCodec()], - dbEncryptionKey: keyBytes, - }) - - Client.register(new NumberCodec()) - - await delayToPropogate() - - const boConvo = await bo.conversations.newConversation(alix.inboxId) - await delayToPropogate() - await boConvo.send( - { topNumber: { bottomNumber: 12 } }, - { contentType: ContentTypeNumber } - ) - - await alix.conversations.syncAllConversations() - const alixConvo = await alix.conversations.findConversation(boConvo.id) +test('can stream conversations and messages including reactions', async () => { + const [alix, bo] = await createClients(2) - const messages = await alixConvo!.messages() - assert(messages.length === 1, 'did not get messages') + const allBoMessages: any[] = [] + const group = await alix.conversations.newGroup([bo.inboxId]) + const dm = await alix.conversations.findOrCreateDm(bo.inboxId) - const message = messages[0] - const messageContent = message.content() + await bo.conversations.syncAllConversations() assert( - typeof messageContent === 'object' && - 'topNumber' in messageContent && - messageContent.topNumber.bottomNumber === 12, - 'did not get content properly: ' + JSON.stringify(messageContent) + (await bo.conversations.list()).length === 2, + `conversation length should be 2` ) - - return true -}) - -test('register and use custom content types with prepare', 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 bo = await Client.createRandom({ - env: 'local', - codecs: [new NumberCodec()], - dbEncryptionKey: keyBytes, - }) - const alix = await Client.createRandom({ - env: 'local', - codecs: [new NumberCodec()], - dbEncryptionKey: keyBytes, + // Start message stream + await bo.conversations.streamAllMessages(async (conversation) => { + allBoMessages.push(conversation) }) - Client.register(new NumberCodec()) - + // Send regular messages + const messageId1 = await group.send({ text: `hello group` }) + const messageId2 = await dm.send({ text: `hello dm` }) await delayToPropogate() - - const boConvo = await bo.conversations.newConversation(alix.inboxId) await delayToPropogate() - await boConvo.prepareMessage( - { topNumber: { bottomNumber: 12 } }, - { contentType: ContentTypeNumber } - ) - await boConvo.publishPreparedMessages() - await alix.conversations.syncAllConversations() - const alixConvo = await alix.conversations.findConversation(boConvo.id) - - const messages = await alixConvo!.messages() - assert(messages.length === 1, 'did not get messages') - - const message = messages[0] - const messageContent = message.content() - - assert( - typeof messageContent === 'object' && - 'topNumber' in messageContent && - messageContent.topNumber.bottomNumber === 12, - 'did not get content properly: ' + JSON.stringify(messageContent) - ) - - return true -}) - -test('handle fallback types appropriately', 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 bob = await Client.createRandom({ - env: 'local', - codecs: [ - new NumberCodecEmptyFallback(), - new NumberCodecUndefinedFallback(), - ], - dbEncryptionKey: keyBytes, + // Send reactions to those messages + await group.send({ + reactionV2: { + action: 'added', + content: '😃', // Reaction V2 + reference: messageId1, + schema: 'unicode', + }, }) - const alix = await Client.createRandom({ - env: 'local', - dbEncryptionKey: keyBytes, - }) - Client.register(new NumberCodecEmptyFallback()) - Client.register(new NumberCodecUndefinedFallback()) - const bobConvo = await bob.conversations.newConversation(alix.inboxId) - - // @ts-ignore - await bobConvo.send(12, { contentType: ContentTypeNumberWithEmptyFallback }) - - // @ts-ignore - await bobConvo.send(12, { - contentType: ContentTypeNumberWithUndefinedFallback, - }) - - await alix.conversations.syncAllConversations() - const aliceConvo = await alix.conversations.findConversation(bobConvo.id) - - const messages = await aliceConvo!.messages() - assert(messages.length === 2, 'did not get messages') - - const messageUndefinedFallback = messages[0] - const messageWithDefinedFallback = messages[1] - - let message1Content = undefined - try { - message1Content = messageUndefinedFallback.content() - } catch { - message1Content = messageUndefinedFallback.fallback - } - - assert( - message1Content === undefined, - 'did not get content properly when empty fallback: ' + - JSON.stringify(message1Content) - ) - - let message2Content = undefined - try { - message2Content = messageWithDefinedFallback.content() - } catch { - message2Content = messageWithDefinedFallback.fallback - } - - assert( - message2Content === '', - 'did not get content properly: ' + JSON.stringify(message2Content) - ) - - return true -}) - -test('can find a conversations by id', async () => { - const [alixClient, boClient] = await createClients(2) - const alixGroup = await alixClient.conversations.newGroup([boClient.inboxId]) - const alixDm = await alixClient.conversations.findOrCreateDm(boClient.inboxId) - - await boClient.conversations.sync() - const boGroup = await boClient.conversations.findConversation(alixGroup.id) - const boDm = await boClient.conversations.findConversation(alixDm.id) - const boDm2 = await boClient.conversations.findConversation( - 'GARBAGE' as ConversationId - ) - - assert(boDm2 === undefined, `bodm2 should be undefined`) - - assert( - boGroup?.id === alixGroup.id, - `bo group id ${boGroup?.id} does not match alix group id ${alixGroup.id}` - ) - - assert( - boDm?.id === alixDm.id, - `bo dm id ${boDm?.id} does not match alix dm id ${alixDm.id}` - ) - - return true -}) - -test('can find a conversation by topic', async () => { - const [alixClient, boClient] = await createClients(2) - const alixGroup = await alixClient.conversations.newGroup([boClient.inboxId]) - const alixDm = await alixClient.conversations.findOrCreateDm(boClient.inboxId) - - await boClient.conversations.sync() - const boGroup = await boClient.conversations.findConversationByTopic( - alixGroup.topic - ) - const boDm = await boClient.conversations.findConversationByTopic( - alixDm.topic - ) - - assert( - boGroup?.id === alixGroup.id, - `bo group topic ${boGroup?.id} does not match alix group topic ${alixGroup.id}` - ) - - assert( - boDm?.id === alixDm.id, - `bo dm topic ${boDm?.id} does not match alix dm topic ${alixDm.id}` - ) - - return true -}) - -test('can find a dm by inbox id', async () => { - const [alixClient, boClient] = await createClients(2) - const alixDm = await alixClient.conversations.findOrCreateDm(boClient.inboxId) - - await boClient.conversations.sync() - const boDm = await boClient.conversations.findDmByInboxId(alixClient.inboxId) - - assert( - boDm?.id === alixDm.id, - `bo dm id ${boDm?.id} does not match alix dm id ${alixDm.id}` - ) - - return true -}) - -test('can find a dm by address', async () => { - const [alixClient, boClient] = await createClients(2) - const alixDm = await alixClient.conversations.findOrCreateDm(boClient.inboxId) - - await boClient.conversations.sync() - const boDm = await boClient.conversations.findDmByIdentity( - alixClient.publicIdentity - ) - - assert( - boDm?.id === alixDm.id, - `bo dm id ${boDm?.id} does not match alix dm id ${alixDm.id}` - ) - - return true -}) - -test('can filter conversations by consent', async () => { - const [alixClient, boClient, caroClient] = await createClients(3) - - // Bo allowed + 1 - const boGroupWithAlixAllowed = await boClient.conversations.newGroup([ - alixClient.inboxId, - ]) - // Bo unknown + 1 - const alixGroupWithBo = await alixClient.conversations.newGroup([ - boClient.inboxId, - ]) - // Bo allowed + 1 - const boDmWithAlixAllowed = await boClient.conversations.findOrCreateDm( - alixClient.inboxId - ) - // Bo unknown + 1 - await caroClient.conversations.findOrCreateDm(boClient.inboxId) - await boClient.conversations.sync() - const boDmWithCaroUnknownThenDenied = - await boClient.conversations.findDmByInboxId(caroClient.inboxId) - const boGroupWithAlixUnknown = await boClient.conversations.findGroup( - alixGroupWithBo.id - ) - - // Bo denied + 1; Bo unknown - 1 - await boDmWithCaroUnknownThenDenied?.updateConsent('denied') - - const boConvos = await boClient.conversations.list() - const boConvosFilteredAllowed = await boClient.conversations.list( - {}, - undefined, - ['allowed'] - ) - const boConvosFilteredUnknown = await boClient.conversations.list( - {}, - undefined, - ['unknown'] - ) - const boConvosFilteredAllowedOrDenied = await boClient.conversations.list( - {}, - undefined, - ['allowed', 'denied'] - ) - - assert( - boConvos.length === 4, - `Conversation length should be 4 but was ${boConvos.length}` - ) - - assert( - boConvosFilteredAllowed - .map((conversation: any) => conversation.id) - .toString() === - [boGroupWithAlixAllowed.id, boDmWithAlixAllowed.id].toString(), - `Conversation allowed should be ${[ - boGroupWithAlixAllowed.id, - boDmWithAlixAllowed.id, - ].toString()} but was ${boConvosFilteredAllowed - .map((convo: any) => convo.id) - .toString()}` - ) - - assert( - boConvosFilteredUnknown - .map((conversation: any) => conversation.id) - .toString() === [boGroupWithAlixUnknown?.id].toString(), - `Conversation unknown filter should be ${[ - boGroupWithAlixUnknown?.id, - ].toString()} but was ${boConvosFilteredUnknown - .map((convo: any) => convo.id) - .toString()}` - ) - - assert( - boConvosFilteredAllowedOrDenied - .map((conversation: any) => conversation.id) - .toString() === - [ - boGroupWithAlixAllowed.id, - boDmWithAlixAllowed.id, - boDmWithCaroUnknownThenDenied?.id, - ].toString(), - `Conversation allowed or denied filter should be ${[ - boGroupWithAlixAllowed.id, - boDmWithAlixAllowed.id, - boDmWithCaroUnknownThenDenied?.id, - ].toString()} but was ${boConvosFilteredAllowedOrDenied - .map((convo: any) => convo.id) - .toString()}` - ) - - return true -}) - -test('can filter sync all by consent', async () => { - const [alixClient, boClient, caroClient] = await createClients(3) - - // Bo allowed + 1 - await boClient.conversations.newGroup([alixClient.inboxId]) - // Bo unknown + 1 - const otherGroup = await alixClient.conversations.newGroup([boClient.inboxId]) - // Bo allowed + 1 - await boClient.conversations.findOrCreateDm(alixClient.inboxId) - // Bo unknown + 1 - await caroClient.conversations.findOrCreateDm(boClient.inboxId) - - await boClient.conversations.sync() - const boDmWithCaro = await boClient.conversations.findDmByInboxId( - caroClient.inboxId - ) - await boClient.conversations.findGroup(otherGroup.id) - - // Bo denied + 1; Bo unknown - 1 - await boDmWithCaro?.updateConsent('denied') - - const boConvos = await boClient.conversations.syncAllConversations() - const boConvosFilteredAllowed = - await boClient.conversations.syncAllConversations(['allowed']) - const boConvosFilteredUnknown = - await boClient.conversations.syncAllConversations(['unknown']) - - const boConvosFilteredAllowedOrDenied = - await boClient.conversations.syncAllConversations(['allowed', 'denied']) - - assert(boConvos === 5, `Conversation length should be 5 but was ${boConvos}`) - assert( - boConvosFilteredAllowed === 3, - `Conversation length should be 3 but was ${boConvosFilteredAllowed}` - ) - assert( - boConvosFilteredUnknown === 2, - `Conversation length should be 2 but was ${boConvosFilteredUnknown}` - ) - - assert( - boConvosFilteredAllowedOrDenied === 4, - `Conversation length should be 4 but was ${boConvosFilteredAllowedOrDenied}` - ) - - return true -}) - -test('can list conversations with params', async () => { - const [alixClient, boClient, caroClient] = await createClients(3) - - const boGroup1 = await boClient.conversations.newGroup([alixClient.inboxId]) - const boGroup2 = await boClient.conversations.newGroup([alixClient.inboxId]) - const boDm1 = await boClient.conversations.findOrCreateDm(alixClient.inboxId) - const boDm2 = await boClient.conversations.findOrCreateDm(caroClient.inboxId) - - await boGroup1.send({ text: `first message` }) - await boGroup1.send({ text: `second message` }) - await boGroup1.send({ text: `third message` }) - await boDm2.send({ text: `third message` }) - await boGroup2.send({ text: `first message` }) - await boDm1.send({ text: `dm message` }) - // Order should be [Dm1, Group2, Dm2, Group1] - - await boClient.conversations.syncAllConversations() - const boConvosOrderLastMessage = await boClient.conversations.list({ - lastMessage: true, + await dm.send({ + reaction: { + action: 'added', + content: '😄', // Reaction V1 + reference: messageId2, + schema: 'unicode', + }, }) - const boGroupsLimit = await boClient.conversations.list({}, 1) - - assert( - boConvosOrderLastMessage.map((group: any) => group.id).toString() === - [boDm1.id, boGroup2.id, boDm2.id, boGroup1.id].toString(), - `Conversation last message order should be ${[ - boDm1.id, - boGroup2.id, - boDm2.id, - boGroup1.id, - ].toString()} but was ${boConvosOrderLastMessage - .map((group: any) => group.id) - .toString()}` - ) - - const messages = await boConvosOrderLastMessage[0].messages() - assert( - messages[0].content() === 'dm message', - `last message 1 should be dm message ${messages[0].content()}` - ) - assert( - boConvosOrderLastMessage[0].lastMessage?.content() === 'dm message', - `last message 2 should be dm message ${boConvosOrderLastMessage[0].lastMessage?.content()}` - ) - assert( - boGroupsLimit.length === 1, - `List length should be 1 but was ${boGroupsLimit.length}` - ) - assert( - boGroupsLimit[0].id === boDm1.id, - `Group should be ${boDm1.id} but was ${boGroupsLimit[0].id}` - ) - - return true -}) - -test('can list groups', async () => { - const [alixClient, boClient, caroClient] = await createClients(3) - - const boGroup = await boClient.conversations.newGroup([alixClient.inboxId]) - await boClient.conversations.newGroup([ - caroClient.inboxId, - alixClient.inboxId, - ]) - await boClient.conversations.findOrCreateDm(caroClient.inboxId) - const boDm = await boClient.conversations.findOrCreateDm(alixClient.inboxId) - - const boConversations = await boClient.conversations.list() - await alixClient.conversations.sync() - const alixConversations = await alixClient.conversations.list() - - assert( - boConversations.length === 4, - `bo conversation lengths should be 4 but was ${boConversations.length}` - ) + await delayToPropogate() + // Validate message stream callbacks (4: 2 messages + 2 reactions) assert( - alixConversations.length === 3, - `alix conversation lengths should be 3 but was ${alixConversations.length}` + allBoMessages.length === 4, + `message stream should have received 4 messages (including reactions), got ${allBoMessages.length}` ) - if ( - boConversations[3].topic !== boGroup.topic || - boConversations[3].version !== ConversationVersion.GROUP || - boConversations[0].version !== ConversationVersion.DM || - boConversations[0].createdAt !== boDm.createdAt - ) { - throw Error('Listed containers should match streamed containers') - } - + // Cancel the streams + bo.conversations.cancelStreamAllMessages() return true }) -test('can list conversation messages', async () => { - const [alixClient, boClient, caroClient] = await createClients(3) - - const boGroup = await boClient.conversations.newGroup([alixClient.inboxId]) - const boDm = await boClient.conversations.findOrCreateDm(caroClient.inboxId) - const boGroupConversation = await boClient.conversations.findConversation( - boGroup.id - ) - const boDmConversation = await boClient.conversations.findConversation( - boDm.id - ) - - await boGroupConversation?.send('hello') - await boGroupConversation?.send('hello') - await boDmConversation?.send('hello') - await boDmConversation?.send('hello') - - const boGroupMessages = await boGroupConversation?.messages() - const boDmMessages = await boDmConversation?.messages() - - assert( - boGroupMessages?.length === 3, - `bo conversation lengths should be 3 but was ${boGroupMessages?.length}` - ) - - assert( - boDmMessages?.length === 3, - `alix conversation lengths should be 3 but was ${boDmMessages?.length}` - ) - - return true -}) +// test('register and use custom content types', 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 bo = await Client.createRandom({ +// env: 'local', +// codecs: [new NumberCodec()], +// dbEncryptionKey: keyBytes, +// }) +// const alix = await Client.createRandom({ +// env: 'local', +// codecs: [new NumberCodec()], +// dbEncryptionKey: keyBytes, +// }) + +// Client.register(new NumberCodec()) + +// await delayToPropogate() + +// const boConvo = await bo.conversations.newConversation(alix.inboxId) +// await delayToPropogate() +// await boConvo.send( +// { topNumber: { bottomNumber: 12 } }, +// { contentType: ContentTypeNumber } +// ) + +// await alix.conversations.syncAllConversations() +// const alixConvo = await alix.conversations.findConversation(boConvo.id) + +// const messages = await alixConvo!.messages() +// assert(messages.length === 1, 'did not get messages') + +// const message = messages[0] +// const messageContent = message.content() + +// assert( +// typeof messageContent === 'object' && +// 'topNumber' in messageContent && +// messageContent.topNumber.bottomNumber === 12, +// 'did not get content properly: ' + JSON.stringify(messageContent) +// ) + +// return true +// }) + +// test('register and use custom content types with prepare', 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 bo = await Client.createRandom({ +// env: 'local', +// codecs: [new NumberCodec()], +// dbEncryptionKey: keyBytes, +// }) +// const alix = await Client.createRandom({ +// env: 'local', +// codecs: [new NumberCodec()], +// dbEncryptionKey: keyBytes, +// }) + +// Client.register(new NumberCodec()) + +// await delayToPropogate() + +// const boConvo = await bo.conversations.newConversation(alix.inboxId) +// await delayToPropogate() +// await boConvo.prepareMessage( +// { topNumber: { bottomNumber: 12 } }, +// { contentType: ContentTypeNumber } +// ) +// await boConvo.publishPreparedMessages() + +// await alix.conversations.syncAllConversations() +// const alixConvo = await alix.conversations.findConversation(boConvo.id) + +// const messages = await alixConvo!.messages() +// assert(messages.length === 1, 'did not get messages') + +// const message = messages[0] +// const messageContent = message.content() + +// assert( +// typeof messageContent === 'object' && +// 'topNumber' in messageContent && +// messageContent.topNumber.bottomNumber === 12, +// 'did not get content properly: ' + JSON.stringify(messageContent) +// ) + +// return true +// }) + +// test('handle fallback types appropriately', 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 bob = await Client.createRandom({ +// env: 'local', +// codecs: [ +// new NumberCodecEmptyFallback(), +// new NumberCodecUndefinedFallback(), +// ], +// dbEncryptionKey: keyBytes, +// }) +// const alix = await Client.createRandom({ +// env: 'local', +// dbEncryptionKey: keyBytes, +// }) +// Client.register(new NumberCodecEmptyFallback()) +// Client.register(new NumberCodecUndefinedFallback()) +// const bobConvo = await bob.conversations.newConversation(alix.inboxId) + +// // @ts-ignore +// await bobConvo.send(12, { contentType: ContentTypeNumberWithEmptyFallback }) + +// // @ts-ignore +// await bobConvo.send(12, { +// contentType: ContentTypeNumberWithUndefinedFallback, +// }) + +// await alix.conversations.syncAllConversations() +// const aliceConvo = await alix.conversations.findConversation(bobConvo.id) + +// const messages = await aliceConvo!.messages() +// assert(messages.length === 2, 'did not get messages') + +// const messageUndefinedFallback = messages[0] +// const messageWithDefinedFallback = messages[1] + +// let message1Content = undefined +// try { +// message1Content = messageUndefinedFallback.content() +// } catch { +// message1Content = messageUndefinedFallback.fallback +// } + +// assert( +// message1Content === undefined, +// 'did not get content properly when empty fallback: ' + +// JSON.stringify(message1Content) +// ) + +// let message2Content = undefined +// try { +// message2Content = messageWithDefinedFallback.content() +// } catch { +// message2Content = messageWithDefinedFallback.fallback +// } + +// assert( +// message2Content === '', +// 'did not get content properly: ' + JSON.stringify(message2Content) +// ) + +// return true +// }) + +// test('can find a conversations by id', async () => { +// const [alixClient, boClient] = await createClients(2) +// const alixGroup = await alixClient.conversations.newGroup([boClient.inboxId]) +// const alixDm = await alixClient.conversations.findOrCreateDm(boClient.inboxId) + +// await boClient.conversations.sync() +// const boGroup = await boClient.conversations.findConversation(alixGroup.id) +// const boDm = await boClient.conversations.findConversation(alixDm.id) +// const boDm2 = await boClient.conversations.findConversation( +// 'GARBAGE' as ConversationId +// ) + +// assert(boDm2 === undefined, `bodm2 should be undefined`) + +// assert( +// boGroup?.id === alixGroup.id, +// `bo group id ${boGroup?.id} does not match alix group id ${alixGroup.id}` +// ) + +// assert( +// boDm?.id === alixDm.id, +// `bo dm id ${boDm?.id} does not match alix dm id ${alixDm.id}` +// ) + +// return true +// }) + +// test('can find a conversation by topic', async () => { +// const [alixClient, boClient] = await createClients(2) +// const alixGroup = await alixClient.conversations.newGroup([boClient.inboxId]) +// const alixDm = await alixClient.conversations.findOrCreateDm(boClient.inboxId) + +// await boClient.conversations.sync() +// const boGroup = await boClient.conversations.findConversationByTopic( +// alixGroup.topic +// ) +// const boDm = await boClient.conversations.findConversationByTopic( +// alixDm.topic +// ) + +// assert( +// boGroup?.id === alixGroup.id, +// `bo group topic ${boGroup?.id} does not match alix group topic ${alixGroup.id}` +// ) + +// assert( +// boDm?.id === alixDm.id, +// `bo dm topic ${boDm?.id} does not match alix dm topic ${alixDm.id}` +// ) + +// return true +// }) + +// test('can find a dm by inbox id', async () => { +// const [alixClient, boClient] = await createClients(2) +// const alixDm = await alixClient.conversations.findOrCreateDm(boClient.inboxId) + +// await boClient.conversations.sync() +// const boDm = await boClient.conversations.findDmByInboxId(alixClient.inboxId) + +// assert( +// boDm?.id === alixDm.id, +// `bo dm id ${boDm?.id} does not match alix dm id ${alixDm.id}` +// ) + +// return true +// }) + +// test('can find a dm by address', async () => { +// const [alixClient, boClient] = await createClients(2) +// const alixDm = await alixClient.conversations.findOrCreateDm(boClient.inboxId) + +// await boClient.conversations.sync() +// const boDm = await boClient.conversations.findDmByIdentity( +// alixClient.publicIdentity +// ) + +// assert( +// boDm?.id === alixDm.id, +// `bo dm id ${boDm?.id} does not match alix dm id ${alixDm.id}` +// ) + +// return true +// }) + +// test('can filter conversations by consent', async () => { +// const [alixClient, boClient, caroClient] = await createClients(3) + +// // Bo allowed + 1 +// const boGroupWithAlixAllowed = await boClient.conversations.newGroup([ +// alixClient.inboxId, +// ]) +// // Bo unknown + 1 +// const alixGroupWithBo = await alixClient.conversations.newGroup([ +// boClient.inboxId, +// ]) +// // Bo allowed + 1 +// const boDmWithAlixAllowed = await boClient.conversations.findOrCreateDm( +// alixClient.inboxId +// ) +// // Bo unknown + 1 +// await caroClient.conversations.findOrCreateDm(boClient.inboxId) +// await boClient.conversations.sync() +// const boDmWithCaroUnknownThenDenied = +// await boClient.conversations.findDmByInboxId(caroClient.inboxId) +// const boGroupWithAlixUnknown = await boClient.conversations.findGroup( +// alixGroupWithBo.id +// ) + +// // Bo denied + 1; Bo unknown - 1 +// await boDmWithCaroUnknownThenDenied?.updateConsent('denied') + +// const boConvos = await boClient.conversations.list() +// const boConvosFilteredAllowed = await boClient.conversations.list( +// {}, +// undefined, +// ['allowed'] +// ) +// const boConvosFilteredUnknown = await boClient.conversations.list( +// {}, +// undefined, +// ['unknown'] +// ) +// const boConvosFilteredAllowedOrDenied = await boClient.conversations.list( +// {}, +// undefined, +// ['allowed', 'denied'] +// ) + +// assert( +// boConvos.length === 4, +// `Conversation length should be 4 but was ${boConvos.length}` +// ) + +// assert( +// boConvosFilteredAllowed +// .map((conversation: any) => conversation.id) +// .toString() === +// [boGroupWithAlixAllowed.id, boDmWithAlixAllowed.id].toString(), +// `Conversation allowed should be ${[ +// boGroupWithAlixAllowed.id, +// boDmWithAlixAllowed.id, +// ].toString()} but was ${boConvosFilteredAllowed +// .map((convo: any) => convo.id) +// .toString()}` +// ) + +// assert( +// boConvosFilteredUnknown +// .map((conversation: any) => conversation.id) +// .toString() === [boGroupWithAlixUnknown?.id].toString(), +// `Conversation unknown filter should be ${[ +// boGroupWithAlixUnknown?.id, +// ].toString()} but was ${boConvosFilteredUnknown +// .map((convo: any) => convo.id) +// .toString()}` +// ) + +// assert( +// boConvosFilteredAllowedOrDenied +// .map((conversation: any) => conversation.id) +// .toString() === +// [ +// boGroupWithAlixAllowed.id, +// boDmWithAlixAllowed.id, +// boDmWithCaroUnknownThenDenied?.id, +// ].toString(), +// `Conversation allowed or denied filter should be ${[ +// boGroupWithAlixAllowed.id, +// boDmWithAlixAllowed.id, +// boDmWithCaroUnknownThenDenied?.id, +// ].toString()} but was ${boConvosFilteredAllowedOrDenied +// .map((convo: any) => convo.id) +// .toString()}` +// ) + +// return true +// }) + +// test('can filter sync all by consent', async () => { +// const [alixClient, boClient, caroClient] = await createClients(3) + +// // Bo allowed + 1 +// await boClient.conversations.newGroup([alixClient.inboxId]) +// // Bo unknown + 1 +// const otherGroup = await alixClient.conversations.newGroup([boClient.inboxId]) +// // Bo allowed + 1 +// await boClient.conversations.findOrCreateDm(alixClient.inboxId) +// // Bo unknown + 1 +// await caroClient.conversations.findOrCreateDm(boClient.inboxId) + +// await boClient.conversations.sync() +// const boDmWithCaro = await boClient.conversations.findDmByInboxId( +// caroClient.inboxId +// ) +// await boClient.conversations.findGroup(otherGroup.id) + +// // Bo denied + 1; Bo unknown - 1 +// await boDmWithCaro?.updateConsent('denied') + +// const boConvos = await boClient.conversations.syncAllConversations() +// const boConvosFilteredAllowed = +// await boClient.conversations.syncAllConversations(['allowed']) +// const boConvosFilteredUnknown = +// await boClient.conversations.syncAllConversations(['unknown']) + +// const boConvosFilteredAllowedOrDenied = +// await boClient.conversations.syncAllConversations(['allowed', 'denied']) + +// assert(boConvos === 5, `Conversation length should be 5 but was ${boConvos}`) +// assert( +// boConvosFilteredAllowed === 3, +// `Conversation length should be 3 but was ${boConvosFilteredAllowed}` +// ) +// assert( +// boConvosFilteredUnknown === 2, +// `Conversation length should be 2 but was ${boConvosFilteredUnknown}` +// ) + +// assert( +// boConvosFilteredAllowedOrDenied === 4, +// `Conversation length should be 4 but was ${boConvosFilteredAllowedOrDenied}` +// ) + +// return true +// }) + +// test('can list conversations with params', async () => { +// const [alixClient, boClient, caroClient] = await createClients(3) + +// const boGroup1 = await boClient.conversations.newGroup([alixClient.inboxId]) +// const boGroup2 = await boClient.conversations.newGroup([alixClient.inboxId]) +// const boDm1 = await boClient.conversations.findOrCreateDm(alixClient.inboxId) +// const boDm2 = await boClient.conversations.findOrCreateDm(caroClient.inboxId) + +// await boGroup1.send({ text: `first message` }) +// await boGroup1.send({ text: `second message` }) +// await boGroup1.send({ text: `third message` }) +// await boDm2.send({ text: `third message` }) +// await boGroup2.send({ text: `first message` }) +// await boDm1.send({ text: `dm message` }) +// // Order should be [Dm1, Group2, Dm2, Group1] + +// await boClient.conversations.syncAllConversations() +// const boConvosOrderLastMessage = await boClient.conversations.list({ +// lastMessage: true, +// }) +// const boGroupsLimit = await boClient.conversations.list({}, 1) + +// assert( +// boConvosOrderLastMessage.map((group: any) => group.id).toString() === +// [boDm1.id, boGroup2.id, boDm2.id, boGroup1.id].toString(), +// `Conversation last message order should be ${[ +// boDm1.id, +// boGroup2.id, +// boDm2.id, +// boGroup1.id, +// ].toString()} but was ${boConvosOrderLastMessage +// .map((group: any) => group.id) +// .toString()}` +// ) + +// const messages = await boConvosOrderLastMessage[0].messages() +// assert( +// messages[0].content() === 'dm message', +// `last message 1 should be dm message ${messages[0].content()}` +// ) +// assert( +// boConvosOrderLastMessage[0].lastMessage?.content() === 'dm message', +// `last message 2 should be dm message ${boConvosOrderLastMessage[0].lastMessage?.content()}` +// ) +// assert( +// boGroupsLimit.length === 1, +// `List length should be 1 but was ${boGroupsLimit.length}` +// ) +// assert( +// boGroupsLimit[0].id === boDm1.id, +// `Group should be ${boDm1.id} but was ${boGroupsLimit[0].id}` +// ) + +// return true +// }) + +// test('can list groups', async () => { +// const [alixClient, boClient, caroClient] = await createClients(3) + +// const boGroup = await boClient.conversations.newGroup([alixClient.inboxId]) +// await boClient.conversations.newGroup([ +// caroClient.inboxId, +// alixClient.inboxId, +// ]) +// await boClient.conversations.findOrCreateDm(caroClient.inboxId) +// const boDm = await boClient.conversations.findOrCreateDm(alixClient.inboxId) + +// const boConversations = await boClient.conversations.list() +// await alixClient.conversations.sync() +// const alixConversations = await alixClient.conversations.list() + +// assert( +// boConversations.length === 4, +// `bo conversation lengths should be 4 but was ${boConversations.length}` +// ) + +// assert( +// alixConversations.length === 3, +// `alix conversation lengths should be 3 but was ${alixConversations.length}` +// ) + +// if ( +// boConversations[3].topic !== boGroup.topic || +// boConversations[3].version !== ConversationVersion.GROUP || +// boConversations[0].version !== ConversationVersion.DM || +// boConversations[0].createdAt !== boDm.createdAt +// ) { +// throw Error('Listed containers should match streamed containers') +// } + +// return true +// }) + +// test('can list conversation messages', async () => { +// const [alixClient, boClient, caroClient] = await createClients(3) + +// const boGroup = await boClient.conversations.newGroup([alixClient.inboxId]) +// const boDm = await boClient.conversations.findOrCreateDm(caroClient.inboxId) +// const boGroupConversation = await boClient.conversations.findConversation( +// boGroup.id +// ) +// const boDmConversation = await boClient.conversations.findConversation( +// boDm.id +// ) + +// await boGroupConversation?.send('hello') +// await boGroupConversation?.send('hello') +// await boDmConversation?.send('hello') +// await boDmConversation?.send('hello') + +// const boGroupMessages = await boGroupConversation?.messages() +// const boDmMessages = await boDmConversation?.messages() + +// assert( +// boGroupMessages?.length === 3, +// `bo conversation lengths should be 3 but was ${boGroupMessages?.length}` +// ) + +// assert( +// boDmMessages?.length === 3, +// `alix conversation lengths should be 3 but was ${boDmMessages?.length}` +// ) + +// return true +// }) test('can stream both conversations and messages at same time', async () => { const [alix, bo] = await createClients(2) @@ -647,580 +701,580 @@ test('can stream both conversations and messages at same time', async () => { return true }) -test('can stream conversation messages', async () => { - const [alixClient, boClient] = await createClients(2) - - const alixGroup = await alixClient.conversations.newGroup([boClient.inboxId]) - const alixDm = await alixClient.conversations.findOrCreateDm(boClient.inboxId) - const alixGroupConversation = await alixClient.conversations.findConversation( - alixGroup.id - ) - const alixDmConversation = await alixClient.conversations.findConversation( - alixDm.id - ) - - let dmMessageCallbacks = 0 - let conversationMessageCallbacks = 0 - await alixGroupConversation?.streamMessages(async () => { - conversationMessageCallbacks++ - }) - - await alixDmConversation?.streamMessages(async () => { - dmMessageCallbacks++ - }) - - await alixGroupConversation?.send({ text: `first message` }) - await alixDmConversation?.send({ text: `first message` }) - - assert( - conversationMessageCallbacks === 1, - 'conversation stream should have received 1 conversation' - ) - assert( - dmMessageCallbacks === 1, - 'message stream should have received 1 message' - ) - - return true -}) - -test('can stream all groups and conversations', async () => { - const [alixClient, boClient, caroClient] = await createClients(3) - - const containers: Conversation[] = [] - await alixClient.conversations.stream( - async (conversation: Conversation) => { - containers.push(conversation) - } - ) - - await boClient.conversations.newGroup([alixClient.inboxId]) - await delayToPropogate() - if ((containers.length as number) !== 1) { - throw Error( - 'Unexpected num conversations (should be 1): ' + containers.length - ) - } - - await boClient.conversations.findOrCreateDm(alixClient.inboxId) - await delayToPropogate() - if ((containers.length as number) !== 2) { - throw Error( - 'Unexpected num conversations (should be 2): ' + containers.length - ) - } - - await alixClient.conversations.findOrCreateDm(caroClient.inboxId) - await delayToPropogate() - if (containers.length !== 3) { - throw Error( - 'Expected conversations length 3 but it is: ' + containers.length - ) - } - - alixClient.conversations.cancelStream() - await delayToPropogate() - - await caroClient.conversations.newGroup([alixClient.inboxId]) - await delayToPropogate() - if ((containers.length as number) !== 3) { - throw Error( - 'Unexpected num conversations (should be 3): ' + containers.length - ) - } - - return true -}) - -test('can streamAll from multiple clients', async () => { - const [alix, bo, caro] = await createClients(3) - - // Setup stream alls - const allBoConversations: any[] = [] - const allAliConversations: any[] = [] - - await bo.conversations.stream(async (conversation) => { - allBoConversations.push(conversation) - }) - await alix.conversations.stream(async (conversation) => { - allAliConversations.push(conversation) - }) - - // Start Caro starts a new conversation. - await caro.conversations.newConversation(alix.inboxId) - await delayToPropogate() - if (allBoConversations.length !== 0) { - throw Error( - 'Unexpected all conversations count for Bo ' + - allBoConversations.length + - ' and Alix had ' + - allAliConversations.length - ) - } - if (allAliConversations.length !== 1) { - throw Error( - 'Unexpected all conversations count ' + allAliConversations.length - ) - } - return true -}) - -test('can streamAll from multiple clients - swapped orderring', async () => { - const [alix, bo, caro] = await createClients(3) - - // Setup stream alls - const allBoConversations: any[] = [] - const allAliConversations: any[] = [] - - await alix.conversations.stream(async (conversation) => { - allAliConversations.push(conversation) - }) - - await bo.conversations.stream(async (conversation) => { - allBoConversations.push(conversation) - }) - - // Start Caro starts a new conversation. - await caro.conversations.newConversation(alix.inboxId) - await delayToPropogate() - if (allBoConversations.length !== 0) { - throw Error( - 'Unexpected all conversations count for Bo ' + - allBoConversations.length + - ' and Alix had ' + - allAliConversations.length - ) - } - if (allAliConversations.length !== 1) { - throw Error( - 'Unexpected all conversations count ' + allAliConversations.length - ) - } - return true -}) - -test('can streamAllMessages from multiple clients', async () => { - const [alix, bo, caro] = await createClients(3) - - // Setup stream - const allBoMessages: any[] = [] - const allAliMessages: any[] = [] - - await bo.conversations.streamAllMessages(async (conversation) => { - allBoMessages.push(conversation) - }) - await alix.conversations.streamAllMessages(async (conversation) => { - allAliMessages.push(conversation) - }) - - // Start Caro starts a new conversation. - const caroConversation = await caro.conversations.newConversation( - alix.inboxId - ) - await caroConversation.send({ text: `Message` }) - await delayToPropogate() - if (allBoMessages.length !== 0) { - throw Error('Unexpected all messages count for Bo ' + allBoMessages.length) - } - - if (allAliMessages.length !== 1) { - throw Error( - 'Unexpected all conversations count for Ali ' + allAliMessages.length - ) - } - - bo.conversations.cancelStreamAllMessages() - alix.conversations.cancelStreamAllMessages() - return true -}) - -test('can streamAllMessages from multiple clients - swapped', async () => { - const [alix, bo, caro] = await createClients(3) - - // Setup stream - const allBoMessages: any[] = [] - const allAliMessages: any[] = [] - const caroGroup = await caro.conversations.newGroup([alix.inboxId]) - - await alix.conversations.streamAllMessages(async (conversation) => { - allAliMessages.push(conversation) - }) - await bo.conversations.streamAllMessages(async (conversation) => { - allBoMessages.push(conversation) - }) - - // Start Caro starts a new conversation. - const caroConvo = await caro.conversations.newConversation(alix.inboxId) - await delayToPropogate() - await caroConvo.send({ text: `Message` }) - await caroGroup.send({ text: `Message` }) - await delayToPropogate() - if (allBoMessages.length !== 0) { - throw Error( - 'Unexpected all conversations count for Bo ' + allBoMessages.length - ) - } - - if (allAliMessages.length !== 2) { - throw Error( - 'Unexpected all conversations count for Ali ' + allAliMessages.length - ) - } - bo.conversations.cancelStreamAllMessages() - alix.conversations.cancelStreamAllMessages() - - return true -}) - -test('can sync consent (expected to fail unless historySyncUrl is set)', async () => { - const [bo] = await createClients(1) - const keyBytes = new Uint8Array([ - 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, - 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, - ]) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const dbDirPath = `${RNFS.DocumentDirectoryPath}/xmtp_db` - const dbDirPath2 = `${RNFS.DocumentDirectoryPath}/xmtp_db2` - const directoryExists = await RNFS.exists(dbDirPath) - if (!directoryExists) { - await RNFS.mkdir(dbDirPath) - } - const directoryExists2 = await RNFS.exists(dbDirPath2) - if (!directoryExists2) { - await RNFS.mkdir(dbDirPath2) - } - const alixWallet = Wallet.createRandom() - - const alix = await Client.create(adaptEthersWalletToSigner(alixWallet), { - env: 'local', - dbEncryptionKey: keyBytes, - dbDirectory: dbDirPath, - historySyncUrl: 'http://10.0.2.2:5558', - }) - - // Create DM conversation - const dm = await alix.conversations.findOrCreateDm(bo.inboxId) - await dm.updateConsent('denied') - const consentState = await dm.consentState() - assert(consentState === 'denied', `Expected 'denied', got ${consentState}`) - - await bo.conversations.sync() - const boDm = await bo.conversations.findConversation(dm.id) - - const alix2 = await Client.create(adaptEthersWalletToSigner(alixWallet), { - env: 'local', - dbEncryptionKey: keyBytes, - dbDirectory: dbDirPath2, - historySyncUrl: 'http://10.0.2.2:5558', - }) - - const state = await alix2.inboxState(true) - assert( - state.installations.length === 2, - `Expected 2 installations, got ${state.installations.length}` - ) - - // Sync conversations - await bo.conversations.sync() - if (boDm) await boDm.sync() - await alix2.preferences.sync() - await alix.conversations.syncAllConversations() - await delayToPropogate(2000) - await alix2.conversations.syncAllConversations() - await delayToPropogate(2000) - - const dm2 = await alix2.conversations.findConversation(dm.id) - const consentState2 = await dm2?.consentState() - assert(consentState2 === 'denied', `Expected 'denied', got ${consentState2}`) - - await alix2.preferences.setConsentState( - new ConsentRecord(dm2!.id, 'conversation_id', 'allowed') - ) - - const convoState = await alix2.preferences.conversationConsentState(dm2!.id) - assert(convoState === 'allowed', `Expected 'allowed', got ${convoState}`) - - const updatedConsentState = await dm2?.consentState() - assert( - updatedConsentState === 'allowed', - `Expected 'allowed', got ${updatedConsentState}` - ) - - return true -}) - -test('can stream consent (expected to fail unless historySyncUrl is set)', async () => { - const [bo] = await createClients(1) - const keyBytes = new Uint8Array([ - 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, - 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, - ]) - const dbDirPath = `${RNFS.DocumentDirectoryPath}/xmtp_db` - const dbDirPath2 = `${RNFS.DocumentDirectoryPath}/xmtp_db2` - - // Ensure the directories exist - if (!(await RNFS.exists(dbDirPath))) { - await RNFS.mkdir(dbDirPath) - } - if (!(await RNFS.exists(dbDirPath2))) { - await RNFS.mkdir(dbDirPath2) - } - - const alixWallet = Wallet.createRandom() - - const alix = await Client.create(adaptEthersWalletToSigner(alixWallet), { - env: 'local', - dbEncryptionKey: keyBytes, - dbDirectory: dbDirPath, - historySyncUrl: 'http://10.0.2.2:5558', - }) - - const alixGroup = await alix.conversations.newGroup([bo.inboxId]) - - const alix2 = await Client.create(adaptEthersWalletToSigner(alixWallet), { - env: 'local', - dbEncryptionKey: keyBytes, - dbDirectory: dbDirPath2, - historySyncUrl: 'http://10.0.2.2:5558', - }) - - await alixGroup.send('Hello') - await alix.conversations.syncAllConversations() - await alix2.conversations.syncAllConversations() - - const alix2Group = await alix2.conversations.findConversation(alixGroup.id) - await delayToPropogate() - - const consent = [] - await alix.preferences.streamConsent(async (entry: ConsentRecord) => { - consent.push(entry) - }) - - await delayToPropogate() - - await alix2Group!.updateConsent('denied') - const dm = await alix2.conversations.newConversation(bo.inboxId) - await dm!.updateConsent('denied') - - await delayToPropogate(3000) - await alix.conversations.syncAllConversations() - await alix2.conversations.syncAllConversations() - - assert( - consent.length === 4, - `Expected 4 consent records, got ${consent.length}` - ) - const updatedConsentState = await alixGroup.consentState() - assert( - updatedConsentState === 'denied', - `Expected 'denied', got ${updatedConsentState}` - ) - - alix.preferences.cancelStreamConsent() - - return true -}) - -test('can preference updates (expected to fail unless historySyncUrl is set)', 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 dbDirPath = `${RNFS.DocumentDirectoryPath}/xmtp_db` - const dbDirPath2 = `${RNFS.DocumentDirectoryPath}/xmtp_db2` - - // Ensure the directories exist - if (!(await RNFS.exists(dbDirPath))) { - await RNFS.mkdir(dbDirPath) - } - if (!(await RNFS.exists(dbDirPath2))) { - await RNFS.mkdir(dbDirPath2) - } - - const alixWallet = Wallet.createRandom() - - const alix = await Client.create(adaptEthersWalletToSigner(alixWallet), { - env: 'local', - dbEncryptionKey: keyBytes, - dbDirectory: dbDirPath, - historySyncUrl: 'http://10.0.2.2:5558', - }) - - const types = [] - await alix.preferences.streamPreferenceUpdates( - async (entry: PreferenceUpdates) => { - types.push(entry) - } - ) - - const alix2 = await Client.create(adaptEthersWalletToSigner(alixWallet), { - env: 'local', - dbEncryptionKey: keyBytes, - dbDirectory: dbDirPath2, - historySyncUrl: 'http://10.0.2.2:5558', - }) - - await alix2.conversations.syncAllConversations() - await delayToPropogate(2000) - await alix.conversations.syncAllConversations() - await delayToPropogate(2000) - - assert( - types.length === 2, - `Expected 2 preference update, got ${types.length}` - ) - - alix.preferences.cancelStreamConsent() - - return true -}) - -test('get all HMAC keys', async () => { - const [alix] = await createClients(1) - - const conversations: Conversation[] = [] - - for (let i = 0; i < 5; i++) { - const [client] = await createClients(1) - const convo = await alix.conversations.newConversation(client.inboxId) - conversations.push(convo) - } - - const { hmacKeys } = await alix.conversations.getHmacKeys() - - const topics = Object.keys(hmacKeys) - conversations.forEach((conversation) => { - assert(topics.includes(conversation.topic), 'topic not found') - }) - - return true -}) - -test('test stream messages in parallel', async () => { - const messages = [] - const [alix, bo, caro, davon] = await createClients(4) - - // Create groups - const alixGroup = await alix.conversations.newGroup([ - caro.inboxId, - bo.inboxId, - ]) - - const caroGroup2 = await caro.conversations.newGroup([ - alix.inboxId, - bo.inboxId, - ]) - - // Sync all clients - await Promise.all([ - alix.conversations.syncAllConversations(), - caro.conversations.syncAllConversations(), - bo.conversations.syncAllConversations(), - ]) - - const boGroup = await bo.conversations.findGroup(alixGroup.id) - const caroGroup = await caro.conversations.findGroup(alixGroup.id) - const boGroup2 = await bo.conversations.findGroup(caroGroup2.id) - const alixGroup2 = await alix.conversations.findGroup(caroGroup2.id) - - // Start listening for messages - console.log('Caro is listening...') - try { - await caro.conversations.streamAllMessages(async (message) => { - messages.push(message.content) - console.log(`Caro received: ${message.content}`) - }) - } catch (error) { - console.error('Error while streaming messages:', error) - } - - await delayToPropogate(1000) // 1 second delay - - // Simulate parallel message sending - await Promise.all([ - (async () => { - console.log('Alix is sending messages...') - for (let i = 0; i < 20; i++) { - const message = `Alix Message ${i}` - await alixGroup.send(message) - await alixGroup2!.send(message) - console.log(`Alix sent: ${message}`) - } - })(), - - (async () => { - console.log('Bo is sending messages...') - for (let i = 0; i < 10; i++) { - const message = `Bo Message ${i}` - await boGroup!.send(message) - await boGroup2!.send(message) - console.log(`Bo sent: ${message}`) - } - })(), - - (async () => { - console.log('Davon is sending spam groups...') - for (let i = 0; i < 10; i++) { - const spamMessage = `Davon Spam Message ${i}` - const spamGroup = await davon.conversations.newGroup([caro.inboxId]) - await spamGroup.send(spamMessage) - console.log(`Davon spam: ${spamMessage}`) - } - })(), - - (async () => { - console.log('Caro is sending messages...') - for (let i = 0; i < 10; i++) { - const message = `Caro Message ${i}` - await caroGroup!.send(message) - await caroGroup2.send(message) - console.log(`Caro sent: ${message}`) - } - })(), - ]) - - // Wait to ensure all messages are processed - await delayToPropogate(2000) - - await assertEqual( - messages.length, - 90, - `Expected 90 messages, got ${messages.length}` - ) - const caroMessagesCount = (await caroGroup!.messages()).length - await assertEqual( - caroMessagesCount, - 40, - 'Caro should have received 40 messages' - ) - - await Promise.all([boGroup!.sync(), alixGroup.sync(), caroGroup!.sync()]) - - const boMessagesCount = (await boGroup!.messages()).length - const alixMessagesCount = (await alixGroup.messages()).length - const caroMessagesCountAfterSync = (await caroGroup!.messages()).length - - await assertEqual(boMessagesCount, 40, 'Bo should have received 40 messages') - await assertEqual( - alixMessagesCount, - 41, - 'Alix should have received 41 messages' - ) - await assertEqual( - caroMessagesCountAfterSync, - 40, - 'Caro should still have 40 messages' - ) - - console.log('Test passed: Streams and messages handled correctly.') - caro.conversations.cancelStreamAllMessages() - return true -}) - -test('test pausedForVersion', async () => { - const [alix, bo] = await createClients(2) - const group = await alix.conversations.newGroup([bo.inboxId]) - const version = await group.pausedForVersion() - assert(version === null, `Expected null, got ${version}`) - return true -}) +// test('can stream conversation messages', async () => { +// const [alixClient, boClient] = await createClients(2) + +// const alixGroup = await alixClient.conversations.newGroup([boClient.inboxId]) +// const alixDm = await alixClient.conversations.findOrCreateDm(boClient.inboxId) +// const alixGroupConversation = await alixClient.conversations.findConversation( +// alixGroup.id +// ) +// const alixDmConversation = await alixClient.conversations.findConversation( +// alixDm.id +// ) + +// let dmMessageCallbacks = 0 +// let conversationMessageCallbacks = 0 +// await alixGroupConversation?.streamMessages(async () => { +// conversationMessageCallbacks++ +// }) + +// await alixDmConversation?.streamMessages(async () => { +// dmMessageCallbacks++ +// }) + +// await alixGroupConversation?.send({ text: `first message` }) +// await alixDmConversation?.send({ text: `first message` }) + +// assert( +// conversationMessageCallbacks === 1, +// 'conversation stream should have received 1 conversation' +// ) +// assert( +// dmMessageCallbacks === 1, +// 'message stream should have received 1 message' +// ) + +// return true +// }) + +// test('can stream all groups and conversations', async () => { +// const [alixClient, boClient, caroClient] = await createClients(3) + +// const containers: Conversation[] = [] +// await alixClient.conversations.stream( +// async (conversation: Conversation) => { +// containers.push(conversation) +// } +// ) + +// await boClient.conversations.newGroup([alixClient.inboxId]) +// await delayToPropogate() +// if ((containers.length as number) !== 1) { +// throw Error( +// 'Unexpected num conversations (should be 1): ' + containers.length +// ) +// } + +// await boClient.conversations.findOrCreateDm(alixClient.inboxId) +// await delayToPropogate() +// if ((containers.length as number) !== 2) { +// throw Error( +// 'Unexpected num conversations (should be 2): ' + containers.length +// ) +// } + +// await alixClient.conversations.findOrCreateDm(caroClient.inboxId) +// await delayToPropogate() +// if (containers.length !== 3) { +// throw Error( +// 'Expected conversations length 3 but it is: ' + containers.length +// ) +// } + +// alixClient.conversations.cancelStream() +// await delayToPropogate() + +// await caroClient.conversations.newGroup([alixClient.inboxId]) +// await delayToPropogate() +// if ((containers.length as number) !== 3) { +// throw Error( +// 'Unexpected num conversations (should be 3): ' + containers.length +// ) +// } + +// return true +// }) + +// test('can streamAll from multiple clients', async () => { +// const [alix, bo, caro] = await createClients(3) + +// // Setup stream alls +// const allBoConversations: any[] = [] +// const allAliConversations: any[] = [] + +// await bo.conversations.stream(async (conversation) => { +// allBoConversations.push(conversation) +// }) +// await alix.conversations.stream(async (conversation) => { +// allAliConversations.push(conversation) +// }) + +// // Start Caro starts a new conversation. +// await caro.conversations.newConversation(alix.inboxId) +// await delayToPropogate() +// if (allBoConversations.length !== 0) { +// throw Error( +// 'Unexpected all conversations count for Bo ' + +// allBoConversations.length + +// ' and Alix had ' + +// allAliConversations.length +// ) +// } +// if (allAliConversations.length !== 1) { +// throw Error( +// 'Unexpected all conversations count ' + allAliConversations.length +// ) +// } +// return true +// }) + +// test('can streamAll from multiple clients - swapped orderring', async () => { +// const [alix, bo, caro] = await createClients(3) + +// // Setup stream alls +// const allBoConversations: any[] = [] +// const allAliConversations: any[] = [] + +// await alix.conversations.stream(async (conversation) => { +// allAliConversations.push(conversation) +// }) + +// await bo.conversations.stream(async (conversation) => { +// allBoConversations.push(conversation) +// }) + +// // Start Caro starts a new conversation. +// await caro.conversations.newConversation(alix.inboxId) +// await delayToPropogate() +// if (allBoConversations.length !== 0) { +// throw Error( +// 'Unexpected all conversations count for Bo ' + +// allBoConversations.length + +// ' and Alix had ' + +// allAliConversations.length +// ) +// } +// if (allAliConversations.length !== 1) { +// throw Error( +// 'Unexpected all conversations count ' + allAliConversations.length +// ) +// } +// return true +// }) + +// test('can streamAllMessages from multiple clients', async () => { +// const [alix, bo, caro] = await createClients(3) + +// // Setup stream +// const allBoMessages: any[] = [] +// const allAliMessages: any[] = [] + +// await bo.conversations.streamAllMessages(async (conversation) => { +// allBoMessages.push(conversation) +// }) +// await alix.conversations.streamAllMessages(async (conversation) => { +// allAliMessages.push(conversation) +// }) + +// // Start Caro starts a new conversation. +// const caroConversation = await caro.conversations.newConversation( +// alix.inboxId +// ) +// await caroConversation.send({ text: `Message` }) +// await delayToPropogate() +// if (allBoMessages.length !== 0) { +// throw Error('Unexpected all messages count for Bo ' + allBoMessages.length) +// } + +// if (allAliMessages.length !== 1) { +// throw Error( +// 'Unexpected all conversations count for Ali ' + allAliMessages.length +// ) +// } + +// bo.conversations.cancelStreamAllMessages() +// alix.conversations.cancelStreamAllMessages() +// return true +// }) + +// test('can streamAllMessages from multiple clients - swapped', async () => { +// const [alix, bo, caro] = await createClients(3) + +// // Setup stream +// const allBoMessages: any[] = [] +// const allAliMessages: any[] = [] +// const caroGroup = await caro.conversations.newGroup([alix.inboxId]) + +// await alix.conversations.streamAllMessages(async (conversation) => { +// allAliMessages.push(conversation) +// }) +// await bo.conversations.streamAllMessages(async (conversation) => { +// allBoMessages.push(conversation) +// }) + +// // Start Caro starts a new conversation. +// const caroConvo = await caro.conversations.newConversation(alix.inboxId) +// await delayToPropogate() +// await caroConvo.send({ text: `Message` }) +// await caroGroup.send({ text: `Message` }) +// await delayToPropogate() +// if (allBoMessages.length !== 0) { +// throw Error( +// 'Unexpected all conversations count for Bo ' + allBoMessages.length +// ) +// } + +// if (allAliMessages.length !== 2) { +// throw Error( +// 'Unexpected all conversations count for Ali ' + allAliMessages.length +// ) +// } +// bo.conversations.cancelStreamAllMessages() +// alix.conversations.cancelStreamAllMessages() + +// return true +// }) + +// test('can sync consent (expected to fail unless historySyncUrl is set)', async () => { +// const [bo] = await createClients(1) +// const keyBytes = new Uint8Array([ +// 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, +// 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, +// ]) +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const dbDirPath = `${RNFS.DocumentDirectoryPath}/xmtp_db` +// const dbDirPath2 = `${RNFS.DocumentDirectoryPath}/xmtp_db2` +// const directoryExists = await RNFS.exists(dbDirPath) +// if (!directoryExists) { +// await RNFS.mkdir(dbDirPath) +// } +// const directoryExists2 = await RNFS.exists(dbDirPath2) +// if (!directoryExists2) { +// await RNFS.mkdir(dbDirPath2) +// } +// const alixWallet = Wallet.createRandom() + +// const alix = await Client.create(adaptEthersWalletToSigner(alixWallet), { +// env: 'local', +// dbEncryptionKey: keyBytes, +// dbDirectory: dbDirPath, +// historySyncUrl: 'http://10.0.2.2:5558', +// }) + +// // Create DM conversation +// const dm = await alix.conversations.findOrCreateDm(bo.inboxId) +// await dm.updateConsent('denied') +// const consentState = await dm.consentState() +// assert(consentState === 'denied', `Expected 'denied', got ${consentState}`) + +// await bo.conversations.sync() +// const boDm = await bo.conversations.findConversation(dm.id) + +// const alix2 = await Client.create(adaptEthersWalletToSigner(alixWallet), { +// env: 'local', +// dbEncryptionKey: keyBytes, +// dbDirectory: dbDirPath2, +// historySyncUrl: 'http://10.0.2.2:5558', +// }) + +// const state = await alix2.inboxState(true) +// assert( +// state.installations.length === 2, +// `Expected 2 installations, got ${state.installations.length}` +// ) + +// // Sync conversations +// await bo.conversations.sync() +// if (boDm) await boDm.sync() +// await alix2.preferences.sync() +// await alix.conversations.syncAllConversations() +// await delayToPropogate(2000) +// await alix2.conversations.syncAllConversations() +// await delayToPropogate(2000) + +// const dm2 = await alix2.conversations.findConversation(dm.id) +// const consentState2 = await dm2?.consentState() +// assert(consentState2 === 'denied', `Expected 'denied', got ${consentState2}`) + +// await alix2.preferences.setConsentState( +// new ConsentRecord(dm2!.id, 'conversation_id', 'allowed') +// ) + +// const convoState = await alix2.preferences.conversationConsentState(dm2!.id) +// assert(convoState === 'allowed', `Expected 'allowed', got ${convoState}`) + +// const updatedConsentState = await dm2?.consentState() +// assert( +// updatedConsentState === 'allowed', +// `Expected 'allowed', got ${updatedConsentState}` +// ) + +// return true +// }) + +// test('can stream consent (expected to fail unless historySyncUrl is set)', async () => { +// const [bo] = await createClients(1) +// const keyBytes = new Uint8Array([ +// 233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64, +// 166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145, +// ]) +// const dbDirPath = `${RNFS.DocumentDirectoryPath}/xmtp_db` +// const dbDirPath2 = `${RNFS.DocumentDirectoryPath}/xmtp_db2` + +// // Ensure the directories exist +// if (!(await RNFS.exists(dbDirPath))) { +// await RNFS.mkdir(dbDirPath) +// } +// if (!(await RNFS.exists(dbDirPath2))) { +// await RNFS.mkdir(dbDirPath2) +// } + +// const alixWallet = Wallet.createRandom() + +// const alix = await Client.create(adaptEthersWalletToSigner(alixWallet), { +// env: 'local', +// dbEncryptionKey: keyBytes, +// dbDirectory: dbDirPath, +// historySyncUrl: 'http://10.0.2.2:5558', +// }) + +// const alixGroup = await alix.conversations.newGroup([bo.inboxId]) + +// const alix2 = await Client.create(adaptEthersWalletToSigner(alixWallet), { +// env: 'local', +// dbEncryptionKey: keyBytes, +// dbDirectory: dbDirPath2, +// historySyncUrl: 'http://10.0.2.2:5558', +// }) + +// await alixGroup.send('Hello') +// await alix.conversations.syncAllConversations() +// await alix2.conversations.syncAllConversations() + +// const alix2Group = await alix2.conversations.findConversation(alixGroup.id) +// await delayToPropogate() + +// const consent = [] +// await alix.preferences.streamConsent(async (entry: ConsentRecord) => { +// consent.push(entry) +// }) + +// await delayToPropogate() + +// await alix2Group!.updateConsent('denied') +// const dm = await alix2.conversations.newConversation(bo.inboxId) +// await dm!.updateConsent('denied') + +// await delayToPropogate(3000) +// await alix.conversations.syncAllConversations() +// await alix2.conversations.syncAllConversations() + +// assert( +// consent.length === 4, +// `Expected 4 consent records, got ${consent.length}` +// ) +// const updatedConsentState = await alixGroup.consentState() +// assert( +// updatedConsentState === 'denied', +// `Expected 'denied', got ${updatedConsentState}` +// ) + +// alix.preferences.cancelStreamConsent() + +// return true +// }) + +// test('can preference updates (expected to fail unless historySyncUrl is set)', 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 dbDirPath = `${RNFS.DocumentDirectoryPath}/xmtp_db` +// const dbDirPath2 = `${RNFS.DocumentDirectoryPath}/xmtp_db2` + +// // Ensure the directories exist +// if (!(await RNFS.exists(dbDirPath))) { +// await RNFS.mkdir(dbDirPath) +// } +// if (!(await RNFS.exists(dbDirPath2))) { +// await RNFS.mkdir(dbDirPath2) +// } + +// const alixWallet = Wallet.createRandom() + +// const alix = await Client.create(adaptEthersWalletToSigner(alixWallet), { +// env: 'local', +// dbEncryptionKey: keyBytes, +// dbDirectory: dbDirPath, +// historySyncUrl: 'http://10.0.2.2:5558', +// }) + +// const types = [] +// await alix.preferences.streamPreferenceUpdates( +// async (entry: PreferenceUpdates) => { +// types.push(entry) +// } +// ) + +// const alix2 = await Client.create(adaptEthersWalletToSigner(alixWallet), { +// env: 'local', +// dbEncryptionKey: keyBytes, +// dbDirectory: dbDirPath2, +// historySyncUrl: 'http://10.0.2.2:5558', +// }) + +// await alix2.conversations.syncAllConversations() +// await delayToPropogate(2000) +// await alix.conversations.syncAllConversations() +// await delayToPropogate(2000) + +// assert( +// types.length === 2, +// `Expected 2 preference update, got ${types.length}` +// ) + +// alix.preferences.cancelStreamConsent() + +// return true +// }) + +// test('get all HMAC keys', async () => { +// const [alix] = await createClients(1) + +// const conversations: Conversation[] = [] + +// for (let i = 0; i < 5; i++) { +// const [client] = await createClients(1) +// const convo = await alix.conversations.newConversation(client.inboxId) +// conversations.push(convo) +// } + +// const { hmacKeys } = await alix.conversations.getHmacKeys() + +// const topics = Object.keys(hmacKeys) +// conversations.forEach((conversation) => { +// assert(topics.includes(conversation.topic), 'topic not found') +// }) + +// return true +// }) + +// test('test stream messages in parallel', async () => { +// const messages = [] +// const [alix, bo, caro, davon] = await createClients(4) + +// // Create groups +// const alixGroup = await alix.conversations.newGroup([ +// caro.inboxId, +// bo.inboxId, +// ]) + +// const caroGroup2 = await caro.conversations.newGroup([ +// alix.inboxId, +// bo.inboxId, +// ]) + +// // Sync all clients +// await Promise.all([ +// alix.conversations.syncAllConversations(), +// caro.conversations.syncAllConversations(), +// bo.conversations.syncAllConversations(), +// ]) + +// const boGroup = await bo.conversations.findGroup(alixGroup.id) +// const caroGroup = await caro.conversations.findGroup(alixGroup.id) +// const boGroup2 = await bo.conversations.findGroup(caroGroup2.id) +// const alixGroup2 = await alix.conversations.findGroup(caroGroup2.id) + +// // Start listening for messages +// console.log('Caro is listening...') +// try { +// await caro.conversations.streamAllMessages(async (message) => { +// messages.push(message.content) +// console.log(`Caro received: ${message.content}`) +// }) +// } catch (error) { +// console.error('Error while streaming messages:', error) +// } + +// await delayToPropogate(1000) // 1 second delay + +// // Simulate parallel message sending +// await Promise.all([ +// (async () => { +// console.log('Alix is sending messages...') +// for (let i = 0; i < 20; i++) { +// const message = `Alix Message ${i}` +// await alixGroup.send(message) +// await alixGroup2!.send(message) +// console.log(`Alix sent: ${message}`) +// } +// })(), + +// (async () => { +// console.log('Bo is sending messages...') +// for (let i = 0; i < 10; i++) { +// const message = `Bo Message ${i}` +// await boGroup!.send(message) +// await boGroup2!.send(message) +// console.log(`Bo sent: ${message}`) +// } +// })(), + +// (async () => { +// console.log('Davon is sending spam groups...') +// for (let i = 0; i < 10; i++) { +// const spamMessage = `Davon Spam Message ${i}` +// const spamGroup = await davon.conversations.newGroup([caro.inboxId]) +// await spamGroup.send(spamMessage) +// console.log(`Davon spam: ${spamMessage}`) +// } +// })(), + +// (async () => { +// console.log('Caro is sending messages...') +// for (let i = 0; i < 10; i++) { +// const message = `Caro Message ${i}` +// await caroGroup!.send(message) +// await caroGroup2.send(message) +// console.log(`Caro sent: ${message}`) +// } +// })(), +// ]) + +// // Wait to ensure all messages are processed +// await delayToPropogate(2000) + +// await assertEqual( +// messages.length, +// 90, +// `Expected 90 messages, got ${messages.length}` +// ) +// const caroMessagesCount = (await caroGroup!.messages()).length +// await assertEqual( +// caroMessagesCount, +// 40, +// 'Caro should have received 40 messages' +// ) + +// await Promise.all([boGroup!.sync(), alixGroup.sync(), caroGroup!.sync()]) + +// const boMessagesCount = (await boGroup!.messages()).length +// const alixMessagesCount = (await alixGroup.messages()).length +// const caroMessagesCountAfterSync = (await caroGroup!.messages()).length + +// await assertEqual(boMessagesCount, 40, 'Bo should have received 40 messages') +// await assertEqual( +// alixMessagesCount, +// 41, +// 'Alix should have received 41 messages' +// ) +// await assertEqual( +// caroMessagesCountAfterSync, +// 40, +// 'Caro should still have 40 messages' +// ) + +// console.log('Test passed: Streams and messages handled correctly.') +// caro.conversations.cancelStreamAllMessages() +// return true +// }) + +// test('test pausedForVersion', async () => { +// const [alix, bo] = await createClients(2) +// const group = await alix.conversations.newGroup([bo.inboxId]) +// const version = await group.pausedForVersion() +// assert(version === null, `Expected null, got ${version}`) +// return true +// })