Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disappearing Messages #603

Merged
merged 22 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/silly-foxes-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@xmtp/react-native-sdk": patch
---

Disappearing Messages
DM membership adds (increases message length by 1 for dm creators)
Bug fix key package issues
Bug fix rate limiting
Mark addAccount as a delicate API
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import expo.modules.xmtpreactnativesdk.wrappers.ConversationParamsWrapper
import expo.modules.xmtpreactnativesdk.wrappers.CreateGroupParamsWrapper
import expo.modules.xmtpreactnativesdk.wrappers.MessageWrapper
import expo.modules.xmtpreactnativesdk.wrappers.DecryptedLocalAttachment
import expo.modules.xmtpreactnativesdk.wrappers.DisappearingMessageSettingsWrapper
import expo.modules.xmtpreactnativesdk.wrappers.DmWrapper
import expo.modules.xmtpreactnativesdk.wrappers.EncryptedLocalAttachment
import expo.modules.xmtpreactnativesdk.wrappers.GroupWrapper
Expand Down Expand Up @@ -55,6 +56,7 @@ import org.xmtp.android.library.codecs.EncryptedEncodedContent
import org.xmtp.android.library.codecs.RemoteAttachment
import org.xmtp.android.library.codecs.decoded
import org.xmtp.android.library.hexToByteArray
import org.xmtp.android.library.libxmtp.DisappearingMessageSettings
import org.xmtp.android.library.libxmtp.GroupPermissionPreconfiguration
import org.xmtp.android.library.libxmtp.Message
import org.xmtp.android.library.libxmtp.PermissionOption
Expand Down Expand Up @@ -387,7 +389,7 @@ class XMTPModule : Module() {
}
}

AsyncFunction("addAccount") Coroutine { installationId: String, newAddress: String, walletParams: String ->
AsyncFunction("addAccount") Coroutine { installationId: String, newAddress: String, walletParams: String, allowReassignInboxId: Boolean ->
withContext(Dispatchers.IO) {
logV("addAccount")
val client = clients[installationId] ?: throw XMTPException("No client")
Expand All @@ -402,7 +404,7 @@ class XMTPModule : Module() {
)
signer = reactSigner

client.addAccount(reactSigner)
client.addAccount(reactSigner, allowReassignInboxId)
signer = null
}
}
Expand Down Expand Up @@ -783,20 +785,30 @@ class XMTPModule : Module() {
}
}

AsyncFunction("findOrCreateDm") Coroutine { installationId: String, peerAddress: String ->
AsyncFunction("findOrCreateDm") Coroutine { installationId: String, peerAddress: String, disappearStartingAtNs: Long?, retentionDurationInNs: Long? ->
withContext(Dispatchers.IO) {
logV("findOrCreateDm")
val client = clients[installationId] ?: throw XMTPException("No client")
val dm = client.conversations.findOrCreateDm(peerAddress)
val settings = if (disappearStartingAtNs != null && retentionDurationInNs != null) {
DisappearingMessageSettings(disappearStartingAtNs, retentionDurationInNs)
} else {
null
}
val dm = client.conversations.findOrCreateDm(peerAddress, settings)
DmWrapper.encode(client, dm)
}
}

AsyncFunction("findOrCreateDmWithInboxId") Coroutine { installationId: String, peerInboxId: String ->
AsyncFunction("findOrCreateDmWithInboxId") Coroutine { installationId: String, peerInboxId: String, disappearStartingAtNs: Long?, retentionDurationInNs: Long? ->
withContext(Dispatchers.IO) {
logV("findOrCreateDmWithInboxId")
val client = clients[installationId] ?: throw XMTPException("No client")
val dm = client.conversations.findOrCreateDmWithInboxId(peerInboxId)
val settings = if (disappearStartingAtNs != null && retentionDurationInNs != null) {
DisappearingMessageSettings(disappearStartingAtNs, retentionDurationInNs)
} else {
null
}
val dm = client.conversations.findOrCreateDmWithInboxId(peerInboxId, settings)
DmWrapper.encode(client, dm)
}
}
Expand All @@ -817,6 +829,7 @@ class XMTPModule : Module() {
createGroupParams.groupName,
createGroupParams.groupImageUrlSquare,
createGroupParams.groupDescription,
createGroupParams.disappearingMessageSettings
)
GroupWrapper.encode(client, group)
}
Expand All @@ -838,6 +851,7 @@ class XMTPModule : Module() {
createGroupParams.groupName,
createGroupParams.groupImageUrlSquare,
createGroupParams.groupDescription,
createGroupParams.disappearingMessageSettings
)
GroupWrapper.encode(client, group)
}
Expand All @@ -859,6 +873,7 @@ class XMTPModule : Module() {
createGroupParams.groupName,
createGroupParams.groupImageUrlSquare,
createGroupParams.groupDescription,
createGroupParams.disappearingMessageSettings
)
GroupWrapper.encode(client, group)
}
Expand All @@ -880,6 +895,7 @@ class XMTPModule : Module() {
createGroupParams.groupName,
createGroupParams.groupImageUrlSquare,
createGroupParams.groupDescription,
createGroupParams.disappearingMessageSettings
)
GroupWrapper.encode(client, group)
}
Expand Down Expand Up @@ -1045,6 +1061,52 @@ class XMTPModule : Module() {
}
}

AsyncFunction("disappearingMessageSettings") Coroutine { installationId: String, conversationId: String ->
withContext(Dispatchers.IO) {
logV("disappearingMessageSettings")
val client = clients[installationId] ?: throw XMTPException("No client")
val conversation = client.findConversation(conversationId)
?: throw XMTPException("no conversation found for $conversationId")
val settings = conversation.disappearingMessageSettings
settings?.let { DisappearingMessageSettingsWrapper.encode(it) }
}
}

AsyncFunction("isDisappearingMessagesEnabled") Coroutine { installationId: String, conversationId: String ->
withContext(Dispatchers.IO) {
logV("isDisappearingMessagesEnabled")
val client = clients[installationId] ?: throw XMTPException("No client")
val conversation = client.findConversation(conversationId)
?: throw XMTPException("no conversation found for $conversationId")
conversation.isDisappearingMessagesEnabled
}
}

AsyncFunction("clearDisappearingMessageSettings") Coroutine { installationId: String, conversationId: String ->
withContext(Dispatchers.IO) {
logV("clearDisappearingMessageSettings")
val client = clients[installationId] ?: throw XMTPException("No client")
val conversation = client.findConversation(conversationId)
?: throw XMTPException("no conversation found for $conversationId")
conversation.clearDisappearingMessageSettings()
}
}

AsyncFunction("updateDisappearingMessageSettings") Coroutine { installationId: String, conversationId: String, startAtNs: Long, durationInNs: Long ->
withContext(Dispatchers.IO) {
logV("updateDisappearingMessageSettings")
val client = clients[installationId] ?: throw XMTPException("No client")
val conversation = client.findConversation(conversationId)
?: throw XMTPException("no conversation found for $conversationId")
conversation.updateDisappearingMessageSettings(
DisappearingMessageSettings(
startAtNs,
durationInNs
)
)
}
}

AsyncFunction("isGroupActive") Coroutine { installationId: String, groupId: String ->
withContext(Dispatchers.IO) {
logV("isGroupActive")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
package expo.modules.xmtpreactnativesdk.wrappers

import com.google.gson.JsonParser
import org.xmtp.android.library.libxmtp.DisappearingMessageSettings

class CreateGroupParamsWrapper(
val groupName: String,
val groupImageUrlSquare: String,
val groupDescription: String,
val disappearingMessageSettings: DisappearingMessageSettings,
) {
companion object {
fun createGroupParamsFromJson(authParams: String): CreateGroupParamsWrapper {
val jsonOptions = JsonParser.parseString(authParams).asJsonObject
val settings = DisappearingMessageSettings(
if (jsonOptions.has("disappearStartingAtNs")) jsonOptions.get("disappearStartingAtNs").asLong else 0,
if (jsonOptions.has("retentionDurationInNs")) jsonOptions.get("retentionDurationInNs").asLong else 0
)

return CreateGroupParamsWrapper(
if (jsonOptions.has("name")) jsonOptions.get("name").asString else "",
if (jsonOptions.has("imageUrlSquare")) jsonOptions.get("imageUrlSquare").asString else "",
if (jsonOptions.has("description")) jsonOptions.get("description").asString else "",
settings
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package expo.modules.xmtpreactnativesdk.wrappers

import com.google.gson.GsonBuilder
import org.xmtp.android.library.libxmtp.DisappearingMessageSettings

class DisappearingMessageSettingsWrapper {

companion object {
fun encode(model: DisappearingMessageSettings): String {
val gson = GsonBuilder().create()
val message = encodeMap(model)
return gson.toJson(message)
}

fun encodeMap(model: DisappearingMessageSettings): Map<String, Any> = mapOf(
"disappearStartingAtNs" to model.disappearStartingAtNs,
"retentionDurationInNs" to model.retentionDurationInNs,
)
}
}
28 changes: 10 additions & 18 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ PODS:
- hermes-engine/Pre-built (= 0.71.14)
- hermes-engine/Pre-built (0.71.14)
- libevent (2.1.12)
- LibXMTP (3.0.26)
- LibXMTP (3.0.27)
- MessagePacker (0.4.7)
- MMKV (2.0.2):
- MMKVCore (~> 2.0.2)
Expand Down Expand Up @@ -448,18 +448,18 @@ PODS:
- SQLCipher/standard (4.5.7):
- SQLCipher/common
- SwiftProtobuf (1.28.2)
- XMTP (3.0.29):
- XMTP (3.0.30):
- Connect-Swift (= 1.0.0)
- CryptoSwift (= 1.8.3)
- CSecp256k1 (~> 0.2)
- LibXMTP (= 3.0.26)
- LibXMTP (= 3.0.27)
- SQLCipher (= 4.5.7)
- XMTPReactNative (3.1.12):
- CSecp256k1 (~> 0.2)
- ExpoModulesCore
- MessagePacker
- SQLCipher (= 4.5.7)
- XMTP (= 3.0.29)
- XMTP (= 3.0.30)
- Yoga (1.14.0)

DEPENDENCIES:
Expand Down Expand Up @@ -530,7 +530,6 @@ DEPENDENCIES:
- RNFS (from `../node_modules/react-native-fs`)
- RNScreens (from `../node_modules/react-native-screens`)
- RNSVG (from `../node_modules/react-native-svg`)
- XMTP (from `https://github.com/xmtp/xmtp-ios.git`, commit `77940ee4790390154248d0fed0a2d5316fd99b3b`)
- XMTPReactNative (from `../../ios`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)

Expand All @@ -549,6 +548,7 @@ SPEC REPOS:
- OpenSSL-Universal
- SQLCipher
- SwiftProtobuf
- XMTP

EXTERNAL SOURCES:
boost:
Expand Down Expand Up @@ -679,21 +679,13 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-screens"
RNSVG:
:path: "../node_modules/react-native-svg"
XMTP:
:commit: 77940ee4790390154248d0fed0a2d5316fd99b3b
:git: https://github.com/xmtp/xmtp-ios.git
XMTPReactNative:
:path: "../../ios"
Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"

CHECKOUT OPTIONS:
XMTP:
:commit: 77940ee4790390154248d0fed0a2d5316fd99b3b
:git: https://github.com/xmtp/xmtp-ios.git

SPEC CHECKSUMS:
boost: 7dcd2de282d72e344012f7d6564d024930a6a440
boost: 57d2868c099736d80fcd648bf211b4431e51a558
CoinbaseWalletSDK: ea1f37512bbc69ebe07416e3b29bf840f5cc3152
CoinbaseWalletSDKExpo: c79420eb009f482f768c23b6768fc5b2d7c98777
Connect-Swift: 84e043b904f63dc93a2c01c6c125da25e765b50d
Expand All @@ -719,7 +711,7 @@ SPEC CHECKSUMS:
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
LibXMTP: e80c8c226e67d8c820e81c5a2bfa1934ab5d263c
LibXMTP: 312922ac85f2b20983ba14beb08722b08002ded4
MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02
MMKV: 3eacda84cd1c4fc95cf848d3ecb69d85ed56006c
MMKVCore: 508b4d3a8ce031f1b5c8bd235f0517fb3f4c73a9
Expand Down Expand Up @@ -770,10 +762,10 @@ SPEC CHECKSUMS:
RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396
SQLCipher: 5e6bfb47323635c8b657b1b27d25c5f1baf63bf5
SwiftProtobuf: 4dbaffec76a39a8dc5da23b40af1a5dc01a4c02d
XMTP: 5815a5886b5a698e910d7eb49c11681e9001c931
XMTPReactNative: 68152197c8135a6e0fe03637e7cde6bdd896d75c
XMTP: 49cba75672b7a8e1962acb5202d9e6c839fca984
XMTPReactNative: 1b030fd3a857084edcf9055965bbc1d7cb77b4f6
Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9

PODFILE CHECKSUM: bb988e1a087fa2d3c0bc34c187128b2c7c6b2e58
PODFILE CHECKSUM: 2d04c11c2661aeaad852cd3ada0b0f1b06e0cf24

COCOAPODS: 1.15.2
49 changes: 49 additions & 0 deletions example/src/tests/clientTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
assert,
createClients,
adaptEthersWalletToSigner,
assertEqual,
} from './test-utils'
import { Client } from '../../../src/index'

Expand Down Expand Up @@ -435,6 +436,54 @@ test('can verify signatures', async () => {
return true
})

test('test add account with existing InboxIds', async () => {
const [alixClient] = await createClients(1)

const keyBytes = new Uint8Array([
233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64,
166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145,
])

const boWallet = Wallet.createRandom()

const boClient = await Client.create(adaptEthersWalletToSigner(boWallet), {
env: 'local',
appVersion: 'Testing/0.0.0',
dbEncryptionKey: keyBytes,
})

let errorThrown = false
try {
await alixClient.addAccount(adaptEthersWalletToSigner(boWallet))
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
errorThrown = true
}

if (!errorThrown) {
throw new Error('Expected addAccount to throw an error but it did not')
}

// Ensure that both clients have different inbox IDs
assert(
alixClient.inboxId !== boClient.inboxId,
'Inbox ids should not be equal'
)

// Forcefully add the boClient account to alixClient
await alixClient.addAccount(adaptEthersWalletToSigner(boWallet), true)

// Retrieve the inbox state and check the number of associated addresses
const state = await alixClient.inboxState(true)
await assertEqual(state.addresses.length, 2, 'Length should be 2')

// Validate that the inbox ID from the address matches alixClient's inbox ID
const inboxId = await alixClient.findInboxIdFromAddress(boClient.address)
await assertEqual(inboxId, alixClient.inboxId, 'InboxIds should be equal')

return true
})

test('can add and remove accounts', async () => {
const keyBytes = new Uint8Array([
233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64,
Expand Down
4 changes: 2 additions & 2 deletions example/src/tests/conversationTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,8 +604,8 @@ test('can list conversation messages', async () => {
)

assert(
boDmMessages?.length === 2,
`alix conversation lengths should be 2 but was ${boDmMessages?.length}`
boDmMessages?.length === 3,
`alix conversation lengths should be 3 but was ${boDmMessages?.length}`
)

return true
Expand Down
Loading
Loading