diff --git a/android/build.gradle b/android/build.gradle index d3218cc35..28ed1806b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -98,7 +98,7 @@ repositories { dependencies { implementation project(':expo-modules-core') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" - implementation "org.xmtp:android:0.10.4" + implementation "org.xmtp:android:0.11.2" 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 cf53ec4a4..b5278afbc 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt @@ -51,6 +51,7 @@ import org.xmtp.android.library.messages.getPublicKeyBundle import org.xmtp.android.library.push.XMTPPush import org.xmtp.proto.keystore.api.v1.Keystore.TopicMap.TopicData import org.xmtp.proto.message.api.v1.MessageApiOuterClass +import org.xmtp.proto.message.contents.Invitation.ConsentProofPayload import org.xmtp.proto.message.contents.PrivateKeyOuterClass import java.io.File import java.util.Date @@ -574,11 +575,25 @@ class XMTPModule : Module() { } } - AsyncFunction("createConversation") Coroutine { clientAddress: String, peerAddress: String, contextJson: String -> + AsyncFunction("createConversation") Coroutine { clientAddress: String, peerAddress: String, contextJson: String, consentProofPayload: List -> withContext(Dispatchers.IO) { logV("createConversation: $contextJson") val client = clients[clientAddress] ?: throw XMTPException("No client") val context = JsonParser.parseString(contextJson).asJsonObject + + var consentProof: ConsentProofPayload? = null + if (consentProofPayload.isNotEmpty()) { + val consentProofDataBytes = consentProofPayload.foldIndexed(ByteArray(consentProofPayload.size)) { i, a, v -> + a.apply { + set( + i, + v.toByte() + ) + } + } + consentProof = ConsentProofPayload.parseFrom(consentProofDataBytes) + } + val conversation = client.conversations.newConversation( peerAddress, context = InvitationV1ContextBuilder.buildFromConversation( @@ -595,11 +610,15 @@ class XMTPModule : Module() { else -> mapOf() }, - ) + ), + consentProof ) if (conversation.keyMaterial == null) { logV("Null key material before encode conversation") } + if (conversation.consentProof == null) { + logV("Null consent before encode conversation") + } ConversationWrapper.encode(client, conversation) } } diff --git a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationWrapper.kt b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationWrapper.kt index 6d31ccb8f..97af24141 100644 --- a/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationWrapper.kt +++ b/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ConversationWrapper.kt @@ -8,7 +8,7 @@ import org.xmtp.android.library.Conversation class ConversationWrapper { companion object { - fun encodeToObj(client: Client, conversation: Conversation): Map { + fun encodeToObj(client: Client, conversation: Conversation): Map { val context = when (conversation.version) { Conversation.Version.V2 -> mapOf( "conversationID" to (conversation.conversationId ?: ""), @@ -26,7 +26,8 @@ class ConversationWrapper { "peerAddress" to conversation.peerAddress, "version" to if (conversation.version == Conversation.Version.V1) "v1" else "v2", "conversationID" to (conversation.conversationId ?: ""), - "keyMaterial" to (conversation.keyMaterial?.let { Base64.encodeToString(it, Base64.NO_WRAP) } ?: "") + "keyMaterial" to (conversation.keyMaterial?.let { Base64.encodeToString(it, Base64.NO_WRAP) } ?: ""), + "consentProof" to if (conversation.consentProof != null) Base64.encodeToString(conversation.consentProof?.toByteArray(), Base64.NO_WRAP) else null ) } diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 258c80bb3..660d9a938 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -56,12 +56,12 @@ PODS: - hermes-engine/Pre-built (= 0.71.14) - hermes-engine/Pre-built (0.71.14) - libevent (2.1.12) - - LibXMTP (0.4.4-beta2) + - LibXMTP (0.4.4-beta5) - Logging (1.0.0) - MessagePacker (0.4.7) - - MMKV (1.3.4): - - MMKVCore (~> 1.3.4) - - MMKVCore (1.3.4) + - MMKV (1.3.5): + - MMKVCore (~> 1.3.5) + - MMKVCore (1.3.5) - OpenSSL-Universal (1.1.2200) - RCT-Folly (2021.07.22.00): - boost @@ -322,7 +322,7 @@ PODS: - React-Core - react-native-encrypted-storage (4.0.3): - React-Core - - react-native-get-random-values (1.11.0): + - react-native-get-random-values (1.10.0): - React-Core - react-native-mmkv (2.11.0): - MMKV (>= 1.2.13) @@ -445,16 +445,16 @@ PODS: - GenericJSON (~> 2.0) - Logging (~> 1.0.0) - secp256k1.swift (~> 0.1) - - XMTP (0.10.3): + - XMTP (0.10.9): - Connect-Swift (= 0.12.0) - GzipSwift - - LibXMTP (= 0.4.4-beta2) + - LibXMTP (= 0.4.4-beta5) - web3.swift - XMTPReactNative (0.1.0): - ExpoModulesCore - MessagePacker - secp256k1.swift - - XMTP (= 0.10.3) + - XMTP (= 0.10.9) - Yoga (1.14.0) DEPENDENCIES: @@ -701,11 +701,11 @@ SPEC CHECKSUMS: GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa hermes-engine: d7cc127932c89c53374452d6f93473f1970d8e88 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - LibXMTP: 0c073613451e3850bfcaaab5438b481fe887cd97 + LibXMTP: e2fb601691981900099551ff3e05621bd73dccf1 Logging: 9ef4ecb546ad3169398d5a723bc9bea1c46bef26 MessagePacker: ab2fe250e86ea7aedd1a9ee47a37083edd41fd02 - MMKV: ed58ad794b3f88c24d604a5b74f3fba17fcbaf74 - MMKVCore: a67a1cede26175c413176f404a7cedec43f96a0b + MMKV: 506311d0494023c2f7e0b62cc1f31b7370fa3cfb + MMKVCore: 9e2e5fd529b64a9fe15f1a7afb3d73b2e27b4db9 OpenSSL-Universal: 6e1ae0555546e604dbc632a2b9a24a9c46c41ef6 RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 RCTRequired: e9df143e880d0e879e7a498dc06923d728809c79 @@ -724,7 +724,7 @@ SPEC CHECKSUMS: react-native-blob-util: d8fa1a7f726867907a8e43163fdd8b441d4489ea react-native-config: 86038147314e2e6d10ea9972022aa171e6b1d4d8 react-native-encrypted-storage: db300a3f2f0aba1e818417c1c0a6be549038deb7 - react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06 + react-native-get-random-values: 384787fd76976f5aec9465aff6fa9e9129af1e74 react-native-mmkv: e97c0c79403fb94577e5d902ab1ebd42b0715b43 react-native-netinfo: 8a7fd3f7130ef4ad2fb4276d5c9f8d3f28d2df3d react-native-quick-base64: 777057ea4286f806b00259ede65dc79c7c706320 @@ -751,10 +751,10 @@ SPEC CHECKSUMS: secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634 SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1 web3.swift: 2263d1e12e121b2c42ffb63a5a7beb1acaf33959 - XMTP: bf00ef58d4fbcc8ab740145a6303591adf3fb355 - XMTPReactNative: fae44562e5e457c66fde8e2613e1dfd3c1a71113 + XMTP: 5efac5f69327cc6a4ad5b1f1fb9624b9e8355efe + XMTPReactNative: 5c4baec31d5ef83e5e5a35c33c31e70524db620e Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9 PODFILE CHECKSUM: 95d6ace79946933ecf80684613842ee553dd76a2 -COCOAPODS: 1.14.2 +COCOAPODS: 1.15.2 diff --git a/example/package.json b/example/package.json index ed42a73b2..3a1e0c87a 100644 --- a/example/package.json +++ b/example/package.json @@ -15,6 +15,7 @@ "@thirdweb-dev/react-native": "^0.6.2", "@thirdweb-dev/react-native-compat": "^0.6.2", "@xmtp/frames-client": "^0.3.2", + "@xmtp/proto": "3.54.0", "ethers": "^5.7.2", "expo": "~48.0.18", "expo-document-picker": "^11.5.4", diff --git a/example/src/tests.ts b/example/src/tests.ts index 41fd1e5b1..2a74da803 100644 --- a/example/src/tests.ts +++ b/example/src/tests.ts @@ -1,11 +1,13 @@ +import { sha256 } from '@noble/hashes/sha256' import { FramesClient } from '@xmtp/frames-client' -import { content } from '@xmtp/proto' +import { content, invitation, signature as signatureProto } from '@xmtp/proto' import { createHmac } from 'crypto' +import { ethers } from 'ethers' import ReactNativeBlobUtil from 'react-native-blob-util' import Config from 'react-native-config' import { TextEncoder, TextDecoder } from 'text-encoding' -import { PrivateKeyAccount } from 'viem' -import { privateKeyToAccount } from 'viem/accounts' +import { createWalletClient, custom, PrivateKeyAccount, toHex } from 'viem' +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' import { DecodedMessage } from 'xmtp-react-native-sdk/lib/DecodedMessage' import { @@ -259,9 +261,9 @@ test('can load 1995 conversations from dev network "2k lens convos" account', as xmtpClient.address === '0x209fAEc92D9B072f3E03d6115002d6652ef563cd', 'Address: ' + xmtpClient.address ) - let start = Date.now() + const start = Date.now() const conversations = await xmtpClient.conversations.list() - let end = Date.now() + const end = Date.now() console.log( `Loaded ${conversations.length} conversations in ${end - start}ms` ) @@ -1675,3 +1677,61 @@ test('can handle complex streaming setup with messages from self', async () => { return true }) + +test('can send and receive consent proofs', async () => { + const alixWallet = await ethers.Wallet.createRandom() + const boWallet = await ethers.Wallet.createRandom() + const bo = await Client.create(boWallet, { env: 'local' }) + await delayToPropogate() + const alix = await Client.create(alixWallet, { env: 'local' }) + await delayToPropogate() + + const timestamp = Date.now() + const consentMessage = + 'XMTP : Grant inbox consent to sender\n' + + '\n' + + `Current Time: ${new Date(timestamp).toUTCString()}\n` + + `From Address: ${bo.address}\n` + + '\n' + + 'For more info: https://xmtp.org/signatures/' + const sig = await alixWallet.signMessage(consentMessage) + const consentProof = invitation.ConsentProofPayload.fromPartial({ + payloadVersion: + invitation.ConsentProofPayloadVersion.CONSENT_PROOF_PAYLOAD_VERSION_1, + signature: sig, + timestamp, + }) + + const boConvo = await bo.conversations.newConversation( + alix.address, + undefined, + consentProof + ) + await delayToPropogate() + assert(!!boConvo?.consentProof, 'bo consentProof should exist') + const convos = await alix.conversations.list() + const alixConvo = convos.find((convo) => convo.topic === boConvo.topic) + await delayToPropogate() + assert(!!alixConvo?.consentProof, ' alix consentProof should not exist') + await delayToPropogate() + await alix.contacts.refreshConsentList() + const isAllowed = await alix.contacts.isAllowed(bo.address) + assert(isAllowed, 'bo should be allowed') + return true +}) + +test('can start conversations without consent proofs', async () => { + const bo = await Client.createRandom({ env: 'local' }) + await delayToPropogate() + const alix = await Client.createRandom({ env: 'local' }) + await delayToPropogate() + + const boConvo = await bo.conversations.newConversation(alix.address) + await delayToPropogate() + assert(!boConvo.consentProof, 'consentProof should not exist') + const alixConvo = (await alix.conversations.list())[0] + await delayToPropogate() + assert(!alixConvo.consentProof, 'consentProof should not exist') + await delayToPropogate() + return true +}) diff --git a/example/yarn.lock b/example/yarn.lock index 7e4e6dc5a..3e6f3f7b3 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -4215,7 +4215,7 @@ "@ethersproject/shims@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/shims/-/shims-5.7.0.tgz" + resolved "https://registry.yarnpkg.com/@ethersproject/shims/-/shims-5.7.0.tgz#ee32e543418595774029c5ea6123ea8995e7e154" integrity sha512-WeDptc6oAprov5CCN2LJ/6/+dC9gTonnkdAtLepm/7P5Z+3PRxS5NpfVWmOMs1yE4Vitl2cU8bOPWC0GvGSbVg== "@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": @@ -6843,6 +6843,16 @@ rxjs "^7.8.0" undici "^5.8.1" +"@xmtp/proto@3.54.0": + version "3.54.0" + resolved "https://registry.yarnpkg.com/@xmtp/proto/-/proto-3.54.0.tgz#1b6be9fa2268a51b730bf484af3bf0ebc2621505" + integrity sha512-X0jDRR19/tH0qRB8mM/H/vBueQAK22VZF4QUnDN7TgnbNaOYL5DvSmPfXFH+xzeGKQ5S0zgwc+qFJbI4xoKNHw== + dependencies: + long "^5.2.0" + protobufjs "^7.0.0" + rxjs "^7.8.0" + undici "^5.8.1" + JSONStream@^1.3.5: version "1.3.5" resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" @@ -14471,7 +14481,16 @@ strict-uri-encode@^2.0.0: resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz" integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -14545,7 +14564,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -14559,6 +14578,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz" @@ -15829,7 +15855,7 @@ word-wrap@~1.2.3: resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -15847,6 +15873,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" diff --git a/ios/Wrappers/ConversationWrapper.swift b/ios/Wrappers/ConversationWrapper.swift index b3a63587e..103c6c629 100644 --- a/ios/Wrappers/ConversationWrapper.swift +++ b/ios/Wrappers/ConversationWrapper.swift @@ -17,6 +17,10 @@ struct ConversationWrapper { "metadata": cv2.context.metadata, ] } + var consentProof: String? = nil + if (conversation.consentProof != nil) { + consentProof = try conversation.consentProof?.serializedData().base64EncodedString() + } return [ "clientAddress": client.address, "topic": conversation.topic, @@ -25,7 +29,8 @@ struct ConversationWrapper { "peerAddress": conversation.peerAddress, "version": conversation.version == .v1 ? "v1" : "v2", "conversationID": conversation.conversationID ?? "", - "keyMaterial": conversation.keyMaterial?.base64EncodedString() ?? "" + "keyMaterial": conversation.keyMaterial?.base64EncodedString() ?? "", + "consentProof": consentProof ?? "" ] } diff --git a/ios/XMTPModule.swift b/ios/XMTPModule.swift index 12813269a..cfb89d939 100644 --- a/ios/XMTPModule.swift +++ b/ios/XMTPModule.swift @@ -464,7 +464,7 @@ public class XMTPModule: Module { return prepared.messageID } - AsyncFunction("createConversation") { (clientAddress: String, peerAddress: String, contextJson: String) -> String in + AsyncFunction("createConversation") { (clientAddress: String, peerAddress: String, contextJson: String, consentProofBytes: [UInt8]) -> String in guard let client = await clientsManager.getClient(key: clientAddress) else { throw Error.noClient } @@ -472,10 +472,22 @@ public class XMTPModule: Module { do { let contextData = contextJson.data(using: .utf8)! let contextObj = (try? JSONSerialization.jsonObject(with: contextData) as? [String: Any]) ?? [:] - let conversation = try await client.conversations.newConversation(with: peerAddress, context: .init( - conversationID: contextObj["conversationID"] as? String ?? "", - metadata: contextObj["metadata"] as? [String: String] ?? [:] as [String: String] - )) + var consentProofData:ConsentProofPayload? + if consentProofBytes.count != 0 { + do { + consentProofData = try ConsentProofPayload(serializedData: Data(consentProofBytes)) + } catch { + print("Error: \(error)") + } + } + let conversation = try await client.conversations.newConversation( + with: peerAddress, + context: .init( + conversationID: contextObj["conversationID"] as? String ?? "", + metadata: contextObj["metadata"] as? [String: String] ?? [:] as [String: String] + ), + consentProofPayload:consentProofData + ) return try ConversationWrapper.encode(conversation, client: client) } catch { diff --git a/ios/XMTPReactNative.podspec b/ios/XMTPReactNative.podspec index 0aa246e1c..22a28797f 100644 --- a/ios/XMTPReactNative.podspec +++ b/ios/XMTPReactNative.podspec @@ -26,5 +26,5 @@ Pod::Spec.new do |s| s.source_files = "**/*.{h,m,swift}" s.dependency 'secp256k1.swift' s.dependency "MessagePacker" - s.dependency "XMTP", "= 0.10.3" + s.dependency "XMTP", "= 0.10.9" end diff --git a/package.json b/package.json index 58451b78e..0eb37464b 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@ethersproject/bytes": "^5.7.0", "@msgpack/msgpack": "^3.0.0-beta2", "@noble/hashes": "^1.3.3", - "@xmtp/proto": "^3.44.0", + "@xmtp/proto": "3.54.0", "buffer": "^6.0.3", "text-encoding": "^0.7.0" }, diff --git a/src/index.ts b/src/index.ts index d8ace32fb..74cd1e316 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { content, keystore } from '@xmtp/proto' +import { content, invitation, keystore } from '@xmtp/proto' import { EventEmitter, NativeModulesProxy } from 'expo-modules-core' import { Client } from '.' @@ -242,15 +242,22 @@ export async function listBatchMessages( export async function createConversation( client: Client, peerAddress: string, - context?: ConversationContext + context?: ConversationContext, + consentProofPayload?: invitation.ConsentProofPayload ): Promise> { + const consentProofData = consentProofPayload + ? Array.from( + invitation.ConsentProofPayload.encode(consentProofPayload).finish() + ) + : [] return new Conversation( client, JSON.parse( await XMTPModule.createConversation( client.address, getAddress(peerAddress), - JSON.stringify(context || {}) + JSON.stringify(context || {}), + consentProofData ) ) ) diff --git a/src/lib/Conversation.ts b/src/lib/Conversation.ts index 306b2305d..a36732193 100644 --- a/src/lib/Conversation.ts +++ b/src/lib/Conversation.ts @@ -1,3 +1,6 @@ +import { invitation } from '@xmtp/proto' +import { Buffer } from 'buffer' + import { DecodedMessage } from './DecodedMessage' import * as XMTP from '../index' import { ConversationContext, PreparedLocalMessage } from '../index' @@ -6,6 +9,17 @@ export type SendOptions = { contentType?: XMTP.ContentTypeId } +export interface ConversationParams { + createdAt: number + context?: ConversationContext + topic: string + peerAddress: string + version: string + conversationID?: string + keyMaterial?: string + consentProof?: string +} + export class Conversation { client: XMTP.Client createdAt: number @@ -18,19 +32,12 @@ export class Conversation { * Base64 encoded key material for the conversation. */ keyMaterial?: string | undefined + /** + * Proof of consent for the conversation, used when a user is subscribing to broadcasts. + */ + consentProof?: invitation.ConsentProofPayload | undefined - constructor( - client: XMTP.Client, - params: { - createdAt: number - context?: ConversationContext - topic: string - peerAddress: string - version: string - conversationID?: string | undefined - keyMaterial?: string | undefined - } - ) { + constructor(client: XMTP.Client, params: ConversationParams) { this.client = client this.createdAt = params.createdAt this.context = params.context @@ -39,6 +46,13 @@ export class Conversation { this.version = params.version this.conversationID = params.conversationID this.keyMaterial = params.keyMaterial + try { + if (params?.consentProof) { + this.consentProof = invitation.ConsentProofPayload.decode( + new Uint8Array(Buffer.from(params.consentProof, 'base64')) + ) + } + } catch {} } get clientAddress(): string { @@ -154,7 +168,7 @@ export class Conversation { this.client.address, this.topic, content, - codec, + codec ) } diff --git a/src/lib/Conversations.ts b/src/lib/Conversations.ts index b7e44af64..d44bda637 100644 --- a/src/lib/Conversations.ts +++ b/src/lib/Conversations.ts @@ -1,7 +1,7 @@ -import type { keystore } from '@xmtp/proto' +import type { invitation, keystore } from '@xmtp/proto' import { Client } from './Client' -import { Conversation } from './Conversation' +import { Conversation, ConversationParams } from './Conversation' import { DecodedMessage } from './DecodedMessage' import { ConversationContext } from '../XMTP.types' import * as XMTPModule from '../index' @@ -55,13 +55,15 @@ export default class Conversations { */ async newConversation( peerAddress: string, - context?: ConversationContext + context?: ConversationContext, + consentProof?: invitation.ConsentProofPayload ): Promise> { const checksumAddress = getAddress(peerAddress) return await XMTPModule.createConversation( this.client, checksumAddress, - context + context, + consentProof ) } @@ -85,7 +87,7 @@ export default class Conversations { conversation, }: { clientAddress: string - conversation: Conversation + conversation: ConversationParams }) => { if (clientAddress !== this.client.address) { return diff --git a/yarn.lock b/yarn.lock index 6a295d414..3bf6cc115 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2384,10 +2384,10 @@ resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.13.tgz#ff34942667a4e19a9f4a0996a76814daac364cf3" integrity sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g== -"@xmtp/proto@^3.44.0": - version "3.44.0" - resolved "https://registry.yarnpkg.com/@xmtp/proto/-/proto-3.44.0.tgz#6750f47869de508c2f3db596bb952e84062988bd" - integrity sha512-5M23JsxONb/qluZF6QMjoXHT67xDRR7pWwHTd1tfw59jS+jXTxm/+v8/UeSOoK47PBSJhDD5I90bAeA5NrLRig== +"@xmtp/proto@3.54.0": + version "3.54.0" + resolved "https://registry.yarnpkg.com/@xmtp/proto/-/proto-3.54.0.tgz#1b6be9fa2268a51b730bf484af3bf0ebc2621505" + integrity sha512-X0jDRR19/tH0qRB8mM/H/vBueQAK22VZF4QUnDN7TgnbNaOYL5DvSmPfXFH+xzeGKQ5S0zgwc+qFJbI4xoKNHw== dependencies: long "^5.2.0" protobufjs "^7.0.0" @@ -7737,7 +7737,16 @@ string-length@^5.0.1: char-regex "^2.0.0" strip-ansi "^7.0.1" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -7811,7 +7820,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -8532,7 +8548,16 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==