diff --git a/android/build.gradle b/android/build.gradle index 893fb66d0..015f1b152 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -95,7 +95,7 @@ repositories { dependencies { implementation project(':expo-modules-core') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" - implementation "org.xmtp:android:0.6.17" + implementation "org.xmtp:android:0.6.20" implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.facebook.react:react-native:0.71.3' implementation "com.daveanthonythomas.moshipack:moshipack:1.0.1" diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt index 9d4e4e6fd..b9bf5da58 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -9,6 +9,8 @@ import com.google.gson.JsonParser import com.google.protobuf.kotlin.toByteString import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition +import expo.modules.xmtpreactnativesdk.wrappers.ConsentWrapper +import expo.modules.xmtpreactnativesdk.wrappers.ConsentWrapper.Companion.consentStateToString import expo.modules.xmtpreactnativesdk.wrappers.ContentJson import expo.modules.xmtpreactnativesdk.wrappers.ConversationWrapper import expo.modules.xmtpreactnativesdk.wrappers.DecodedMessageWrapper @@ -546,23 +548,26 @@ class XMTPModule : Module() { AsyncFunction("refreshConsentList") { clientAddress: String -> val client = clients[clientAddress] ?: throw XMTPException("No client") - client.contacts.refreshConsentList() + val consentList = client.contacts.refreshConsentList() + consentList.entries.map { ConsentWrapper.encode(it.value) } } AsyncFunction("conversationConsentState") { clientAddress: String, conversationTopic: String -> val conversation = findConversation(clientAddress, conversationTopic) ?: throw XMTPException("no conversation found for $conversationTopic") - when (conversation.consentState()) { - ConsentState.ALLOWED -> "allowed" - ConsentState.DENIED -> "denied" - ConsentState.UNKNOWN -> "unknown" - } + consentStateToString(conversation.consentState()) + } + + AsyncFunction("consentList") { clientAddress: String -> + val client = clients[clientAddress] ?: throw XMTPException("No client") + client.contacts.consentList.entries.map { ConsentWrapper.encode(it.value) } } } // // Helpers // + private fun findConversation( clientAddress: String, topic: String, diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConsentWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConsentWrapper.kt new file mode 100644 index 000000000..c80eef381 --- /dev/null +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConsentWrapper.kt @@ -0,0 +1,32 @@ +package expo.modules.xmtpreactnativesdk.wrappers + +import com.google.gson.GsonBuilder +import org.xmtp.android.library.ConsentListEntry +import org.xmtp.android.library.ConsentState +import org.xmtp.android.library.codecs.description +import org.xmtp.android.library.messages.DecryptedMessage + +class ConsentWrapper { + + companion object { + fun encode(model: ConsentListEntry): String { + val gson = GsonBuilder().create() + val message = encodeMap(model) + return gson.toJson(message) + } + + fun encodeMap(model: ConsentListEntry): Map = 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" + } + } + } +} diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 13838025c..784ff3564 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -411,16 +411,16 @@ PODS: - GenericJSON (~> 2.0) - Logging (~> 1.0.0) - secp256k1.swift (~> 0.1) - - XMTP (0.6.14-alpha0): + - XMTP (0.7.2-alpha0): - Connect-Swift (= 0.3.0) - GzipSwift - web3.swift - - XMTPRust (= 0.3.6-beta0) + - XMTPRust (= 0.3.7-beta0) - XMTPReactNative (0.1.0): - ExpoModulesCore - MessagePacker - - XMTP (= 0.6.14-alpha0) - - XMTPRust (0.3.6-beta0) + - XMTP (= 0.7.2-alpha0) + - XMTPRust (0.3.7-beta0) - Yoga (1.14.0) DEPENDENCIES: @@ -668,9 +668,9 @@ SPEC CHECKSUMS: secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634 SwiftProtobuf: b02b5075dcf60c9f5f403000b3b0c202a11b6ae1 web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959 - XMTP: 5314a5151dbfad2e24bb5910e679052916a9faff - XMTPReactNative: 5e393ab43d402bae404f675245cfc66a078a33ff - XMTPRust: 3c958736a4f4ee798e425b5644551f1c948da4b0 + XMTP: 4930b80dc99a6a8ebcf1f292a162c1f316f78c50 + XMTPReactNative: 68c723488857950d10fc8ee969de0baae8f9b2ca + XMTPRust: 8848a2ba761b2c961d666632f2ad27d1082faa93 Yoga: 065f0b74dba4832d6e328238de46eb72c5de9556 PODFILE CHECKSUM: 522d88edc2d5fac4825e60a121c24abc18983367 diff --git a/example/src/tests.ts b/example/src/tests.ts index 50e188eea..4d2c97122 100644 --- a/example/src/tests.ts +++ b/example/src/tests.ts @@ -672,6 +672,21 @@ test('canManagePreferences', async () => { ) } + const boConsentList = await bo.contacts.consentList() + await delayToPropogate() + + if (boConsentList.length !== 1) { + throw new Error(`consent list for bo should 1 not ${boConsentList.length}`) + } + + const boConsentListState = boConsentList[0].permissionType + + if (boConsentListState !== 'denied') { + throw new Error( + `conversations denied by bo should be denied in consent list not ${boConsentListState}` + ) + } + return true }) diff --git a/ios/Wrappers/ConsentWrapper.swift b/ios/Wrappers/ConsentWrapper.swift new file mode 100644 index 000000000..c683925a4 --- /dev/null +++ b/ios/Wrappers/ConsentWrapper.swift @@ -0,0 +1,30 @@ +import Foundation +import XMTP + +struct ConsentWrapper { + static func encodeToObj(_ entry: XMTP.ConsentListEntry) throws -> [String: Any] { + return [ + "type": entry.entryType.rawValue, + "value": entry.value, + "state": consentStateToString(state: entry.consentType), + ] + } + + static func encode(_ entry: XMTP.ConsentListEntry) throws -> String { + let obj = try encodeToObj(entry) + let data = try JSONSerialization.data(withJSONObject: obj) + guard let result = String(data: data, encoding: .utf8) else { + throw WrapperError.encodeError("could not encode consent") + } + return result + } + + static func consentStateToString(state: ConsentState) -> String { + switch state { + case .allowed: return "allowed" + case .denied: return "denied" + case .unknown: return "unknown" + } + } + +} diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index 39dd3c1ab..7a086f37d 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -501,30 +501,41 @@ public class XMTPModule: Module { try await client.contacts.allow(addresses: addresses) } - AsyncFunction("refreshConsentList") { (clientAddress: String) in + AsyncFunction("refreshConsentList") { (clientAddress: String) -> [String] in guard let client = await clientsManager.getClient(key: clientAddress) else { throw Error.noClient } - try await client.contacts.refreshConsentList() + let consentList = try await client.contacts.refreshConsentList() + + return try consentList.entries.compactMap { entry in + try ConsentWrapper.encode(entry.value) + } } AsyncFunction("conversationConsentState") { (clientAddress: String, conversationTopic: String) -> String in guard let conversation = try await findConversation(clientAddress: clientAddress, topic: conversationTopic) else { throw Error.conversationNotFound(conversationTopic) } - switch await conversation.consentState() { - case .allowed: return "allowed" - case .denied: return "denied" - case .unknown: return "unknown" - } + return ConsentWrapper.consentStateToString(state: await conversation.consentState()) } + + AsyncFunction("consentList") { (clientAddress: String) -> [String] in + guard let client = await clientsManager.getClient(key: clientAddress) else { + throw Error.noClient + } + let entries = await client.contacts.consentList.entries + + return try entries.compactMap { entry in + try ConsentWrapper.encode(entry.value) + } + } } // // Helpers // - func createClientConfig(env: String, appVersion: String?) -> XMTP.ClientOptions { + func createClientConfig(env: String, appVersion: String?) -> XMTP.ClientOptions { // Ensure that all codecs have been registered. switch env { case "local": diff --git a/ios/XMTPReactNative.podspec b/ios/XMTPReactNative.podspec index f37f2a985..24024418e 100644 --- a/ios/XMTPReactNative.podspec +++ b/ios/XMTPReactNative.podspec @@ -25,5 +25,5 @@ Pod::Spec.new do |s| s.source_files = "**/*.{h,m,swift}" s.dependency "MessagePacker" - s.dependency "XMTP", "= 0.6.14-alpha0" + s.dependency "XMTP", "= 0.7.2-alpha0" end diff --git a/src/index.ts b/src/index.ts index 72f0b1940..d924bf230 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import { EventEmitter, NativeModulesProxy } from 'expo-modules-core' import { Client } from '.' import { ConversationContext } from './XMTP.types' import XMTPModule from './XMTPModule' +import { ConsentListEntry, ConsentState } from './lib/ConsentListEntry' import { DecryptedLocalAttachment, EncryptedLocalAttachment, @@ -320,7 +321,7 @@ export async function decodeMessage( export async function conversationConsentState( clientAddress: string, conversationTopic: string -): Promise<'allowed' | 'denied' | 'unknown'> { +): Promise { return await XMTPModule.conversationConsentState( clientAddress, conversationTopic @@ -349,8 +350,24 @@ export function allowContacts(clientAddress: string, addresses: string[]) { XMTPModule.allowContacts(clientAddress, addresses) } -export function refreshConsentList(clientAddress: string) { - XMTPModule.refreshConsentList(clientAddress) +export async function refreshConsentList( + clientAddress: string +): Promise { + const consentList = await XMTPModule.refreshConsentList(clientAddress) + + return consentList.map((json: string) => { + return ConsentListEntry.from(json) + }) +} + +export async function consentList( + clientAddress: string +): Promise { + const consentList = await XMTPModule.consentList(clientAddress) + + return consentList.map((json: string) => { + return ConsentListEntry.from(json) + }) } export const emitter = new EventEmitter(XMTPModule ?? NativeModulesProxy.XMTP) @@ -362,3 +379,4 @@ export * from './XMTP.types' export { Query } from './lib/Query' export { XMTPPush } from './lib/XMTPPush' export { DecodedMessage } +export { ConsentListEntry } diff --git a/src/lib/ConsentListEntry.ts b/src/lib/ConsentListEntry.ts new file mode 100644 index 000000000..9adb1392c --- /dev/null +++ b/src/lib/ConsentListEntry.ts @@ -0,0 +1,24 @@ +export type ConsentState = 'allowed' | 'denied' | 'unknown' + +export type ConsentListEntryType = 'address' + +export class ConsentListEntry { + value: string + entryType: ConsentListEntryType + permissionType: ConsentState + + constructor( + value: string, + entryType: ConsentListEntryType, + permissionType: ConsentState + ) { + this.value = value + this.entryType = entryType + this.permissionType = permissionType + } + + static from(json: string): ConsentListEntry { + const entry = JSON.parse(json) + return new ConsentListEntry(entry.value, entry.type, entry.state) + } +} diff --git a/src/lib/Contacts.ts b/src/lib/Contacts.ts index 77480048c..4738fc6d7 100644 --- a/src/lib/Contacts.ts +++ b/src/lib/Contacts.ts @@ -1,4 +1,5 @@ import { Client } from './Client' +import { ConsentListEntry } from './ConsentListEntry' import * as XMTPModule from '../index' export default class Contacts { @@ -9,13 +10,11 @@ export default class Contacts { } async isAllowed(address: string): Promise { - const result = await XMTPModule.isAllowed(this.client.address, address) - return result + return await XMTPModule.isAllowed(this.client.address, address) } async isDenied(address: string): Promise { - const result = await XMTPModule.isDenied(this.client.address, address) - return result + return await XMTPModule.isDenied(this.client.address, address) } deny(addresses: string[]) { @@ -26,7 +25,11 @@ export default class Contacts { XMTPModule.allowContacts(this.client.address, addresses) } - refreshConsentList() { - XMTPModule.refreshConsentList(this.client.address) + async refreshConsentList(): Promise { + return await XMTPModule.refreshConsentList(this.client.address) + } + + async consentList(): Promise { + return await XMTPModule.consentList(this.client.address) } }