Skip to content

Commit

Permalink
Merge pull request #546 from xmtp/np/consent-streaming
Browse files Browse the repository at this point in the history
Add ability to stream consent
  • Loading branch information
nplasterer authored Nov 26, 2024
2 parents c8ecebb + d555cdb commit ef59026
Show file tree
Hide file tree
Showing 21 changed files with 1,212 additions and 754 deletions.
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ repositories {
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
implementation "org.xmtp:android:3.0.8"
implementation "org.xmtp:android:3.0.10"
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.facebook.react:react-native:0.71.3'
implementation "com.daveanthonythomas.moshipack:moshipack:1.0.1"
Expand Down
460 changes: 250 additions & 210 deletions android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package expo.modules.xmtpreactnativesdk.wrappers

import com.google.gson.GsonBuilder
import org.xmtp.android.library.ConsentRecord
import org.xmtp.android.library.ConsentState

class ConsentWrapper {

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

fun encodeMap(model: ConsentRecord): Map<String, Any> = mapOf(
"type" to model.entryType.name.lowercase(),
"value" to model.value.lowercase(),
"state" to consentStateToString(model.consentType),
)

fun consentStateToString(state: ConsentState): String {
return when (state) {
ConsentState.ALLOWED -> "allowed"
ConsentState.DENIED -> "denied"
ConsentState.UNKNOWN -> "unknown"
}
}
}
}
8 changes: 4 additions & 4 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ PODS:
- RNSVG (13.14.0):
- React-Core
- SwiftProtobuf (1.28.2)
- XMTP (3.0.8):
- XMTP (3.0.10):
- Connect-Swift (= 1.0.0)
- CryptoSwift (= 1.8.3)
- CSecp256k1 (~> 0.2)
Expand All @@ -450,7 +450,7 @@ PODS:
- CSecp256k1 (~> 0.2)
- ExpoModulesCore
- MessagePacker
- XMTP (= 3.0.8)
- XMTP (= 3.0.10)
- Yoga (1.14.0)

DEPENDENCIES:
Expand Down Expand Up @@ -747,8 +747,8 @@ SPEC CHECKSUMS:
RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f
RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396
SwiftProtobuf: 4dbaffec76a39a8dc5da23b40af1a5dc01a4c02d
XMTP: f8ee66c4aacc5750ab0f67f5bb86c05cd2513185
XMTPReactNative: 8cda2ca09be81cee8c26d986db65fc6faef57bea
XMTP: 22bd60ccd58801953c1bbc7619b0046b2eef5b97
XMTPReactNative: 48e8bbbb9c1800cd507121bd9918aa4a571e8237
Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9

PODFILE CHECKSUM: 0e6fe50018f34e575d38dc6a1fdf1f99c9596cdd
Expand Down
2 changes: 1 addition & 1 deletion example/src/tests/clientTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ test('can drop client from memory', async () => {
await anotherClient.dropLocalDatabaseConnection()

await client.reconnectLocalDatabase()
await Client.dropClient(anotherClient.inboxId)
await Client.dropClient(anotherClient.installationId)
try {
await anotherClient.reconnectLocalDatabase()
return false
Expand Down
157 changes: 157 additions & 0 deletions example/src/tests/conversationTests.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import RNFS from 'react-native-fs'
import { Test, assert, createClients, delayToPropogate } from './test-utils'
import {
Client,
ConsentRecord,
Conversation,
ConversationId,
ConversationVersion,
} from '../../../src/index'
import { Wallet } from 'ethers'

export const conversationTests: Test[] = []
let counter = 1
Expand Down Expand Up @@ -547,3 +551,156 @@ test('can streamAllMessages from multiple clients - swapped', async () => {

return true
})

test('can sync consent', 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(alixWallet, {
env: 'local',
appVersion: 'Testing/0.0.0',
dbEncryptionKey: keyBytes,
dbDirectory: dbDirPath,
})

// Create DM conversation
const dm = await alix.conversations.findOrCreateDm(bo.address)
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(alixWallet, {
env: 'local',
appVersion: 'Testing/0.0.0',
dbEncryptionKey: keyBytes,
dbDirectory: dbDirPath2,
})

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 alix.conversations.sync()
await alix2.conversations.sync()
await alix2.preferences.syncConsent()
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', 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(alixWallet, {
env: 'local',
appVersion: 'Testing/0.0.0',
dbEncryptionKey: keyBytes,
dbDirectory: dbDirPath,
})

const alixGroup = await alix.conversations.newGroup([bo.address])

const alix2 = await Client.create(alixWallet, {
env: 'local',
appVersion: 'Testing/0.0.0',
dbEncryptionKey: keyBytes,
dbDirectory: dbDirPath2,
})

await alixGroup.send('Hello')
await alix.conversations.sync()
await alix2.conversations.sync()
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.address)
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
})
10 changes: 5 additions & 5 deletions example/src/tests/groupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {
Group,
GroupUpdatedContent,
GroupUpdatedCodec,
ConsentListEntry,
DecodedMessage,
ConsentRecord,
} from '../../../src/index'

export const groupTests: Test[] = []
Expand Down Expand Up @@ -1185,7 +1185,7 @@ test('can group consent', async () => {
)

await bo.preferences.setConsentState(
new ConsentListEntry(group.id, 'conversation_id', 'denied')
new ConsentRecord(group.id, 'conversation_id', 'denied')
)
const isDenied = await bo.preferences.conversationConsentState(group.id)
assert(isDenied === 'denied', `bo group should be denied but was ${isDenied}`)
Expand Down Expand Up @@ -1219,7 +1219,7 @@ test('can allow and deny a inbox id', async () => {
)

await bo.preferences.setConsentState(
new ConsentListEntry(alix.inboxId, 'inbox_id', 'allowed')
new ConsentRecord(alix.inboxId, 'inbox_id', 'allowed')
)

let alixMember = (await boGroup.members()).find(
Expand All @@ -1243,7 +1243,7 @@ test('can allow and deny a inbox id', async () => {
)

await bo.preferences.setConsentState(
new ConsentListEntry(alix.inboxId, 'inbox_id', 'denied')
new ConsentRecord(alix.inboxId, 'inbox_id', 'denied')
)

alixMember = (await boGroup.members()).find(
Expand All @@ -1261,7 +1261,7 @@ test('can allow and deny a inbox id', async () => {
)

await bo.preferences.setConsentState(
new ConsentListEntry(alix.address, 'address', 'allowed')
new ConsentRecord(alix.address, 'address', 'allowed')
)

isAddressAllowed = await bo.preferences.addressConsentState(alix.address)
Expand Down
4 changes: 2 additions & 2 deletions ios/Wrappers/ConsentWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import Foundation
import XMTP

struct ConsentWrapper {
static func encodeToObj(_ entry: XMTP.ConsentListEntry) throws -> [String: Any] {
static func encodeToObj(_ entry: XMTP.ConsentRecord) throws -> [String: Any] {
return [
"type": entry.entryType.rawValue,
"value": entry.value,
"state": consentStateToString(state: entry.consentType),
]
}

static func encode(_ entry: XMTP.ConsentListEntry) throws -> String {
static func encode(_ entry: XMTP.ConsentRecord) throws -> String {
let obj = try encodeToObj(entry)
let data = try JSONSerialization.data(withJSONObject: obj)
guard let result = String(data: data, encoding: .utf8) else {
Expand Down
Loading

0 comments on commit ef59026

Please sign in to comment.