Skip to content

Commit

Permalink
Merge pull request #86 from xmtp/daniel-shape-fixes
Browse files Browse the repository at this point in the history
fix: misc missing properties and type fixes
  • Loading branch information
dmccartney authored Aug 20, 2023
2 parents dd691b4 + cb51b4d commit 630af81
Show file tree
Hide file tree
Showing 13 changed files with 360 additions and 202 deletions.
89 changes: 47 additions & 42 deletions android/src/main/java/expo/modules/xmtpreactnativesdk/XMTPModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package expo.modules.xmtpreactnativesdk
import android.util.Base64
import android.util.Base64.NO_WRAP
import android.util.Log
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.ConversationWithClientAddress
import expo.modules.xmtpreactnativesdk.wrappers.ConversationWrapper
import expo.modules.xmtpreactnativesdk.wrappers.ContentJson
import expo.modules.xmtpreactnativesdk.wrappers.ConversationWrapper
import expo.modules.xmtpreactnativesdk.wrappers.DecodedMessageWrapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand All @@ -30,9 +30,7 @@ import org.xmtp.android.library.messages.PrivateKeyBuilder
import org.xmtp.android.library.messages.Signature
import org.xmtp.android.library.push.XMTPPush
import org.xmtp.proto.keystore.api.v1.Keystore.TopicMap.TopicData
import org.xmtp.proto.message.contents.Content.EncodedContent
import org.xmtp.proto.message.contents.PrivateKeyOuterClass
import org.xmtp.proto.message.contents.SignatureOuterClass
import java.util.Date
import java.util.UUID
import kotlin.coroutines.Continuation
Expand All @@ -50,7 +48,7 @@ class ReactNativeSigner(var module: XMTPModule, override var address: String) :
continuations.remove(id)
return
}
val sig = SignatureOuterClass.Signature.newBuilder().also {
val sig = Signature.newBuilder().also {
it.ecdsaCompact = it.ecdsaCompact.toBuilder().also { builder ->
builder.bytes = signatureData.take(64).toByteArray().toByteString()
builder.recovery = signatureData[64].toInt()
Expand Down Expand Up @@ -107,7 +105,7 @@ class XMTPModule : Module() {
private var clients: MutableMap<String, Client> = mutableMapOf()
private var xmtpPush: XMTPPush? = null
private var signer: ReactNativeSigner? = null
private val isDebugEnabled = BuildConfig.DEBUG; // TODO: consider making this configurable
private val isDebugEnabled = BuildConfig.DEBUG // TODO: consider making this configurable
private val conversations: MutableMap<String, Conversation> = mutableMapOf()
private val subscriptions: MutableMap<String, Job> = mutableMapOf()

Expand Down Expand Up @@ -177,7 +175,6 @@ class XMTPModule : Module() {
// Export the conversation's serialized topic data.
AsyncFunction("exportConversationTopicData") { clientAddress: String, topic: String ->
logV("exportConversationTopicData")
val client = clients[clientAddress] ?: throw XMTPException("No client")
val conversation = findConversation(clientAddress, topic)
?: throw XMTPException("no conversation found for $topic")
Base64.encodeToString(conversation.toTopicData().toByteArray(), NO_WRAP)
Expand All @@ -190,7 +187,7 @@ class XMTPModule : Module() {
val data = TopicData.parseFrom(Base64.decode(topicData, NO_WRAP))
val conversation = client.conversations.importTopicData(data)
conversations[conversation.cacheKey(clientAddress)] = conversation
ConversationWrapper.encode(ConversationWithClientAddress(client, conversation))
ConversationWrapper.encode(client, conversation)
}

//
Expand All @@ -208,7 +205,7 @@ class XMTPModule : Module() {
val conversationList = client.conversations.list()
conversationList.map { conversation ->
conversations[conversation.cacheKey(clientAddress)] = conversation
ConversationWrapper.encode(ConversationWithClientAddress(client, conversation))
ConversationWrapper.encode(client, conversation)
}
}

Expand All @@ -223,7 +220,7 @@ class XMTPModule : Module() {
val afterDate = if (after != null) Date(after) else null

conversation.messages(limit = limit, before = beforeDate, after = afterDate)
.map { DecodedMessageWrapper.encode(it) }
.map { DecodedMessageWrapper.encode(it, topic) }
}

AsyncFunction("loadBatchMessages") { clientAddress: String, topics: List<String> ->
Expand Down Expand Up @@ -258,10 +255,11 @@ class XMTPModule : Module() {
}

client.conversations.listBatchMessages(topicsList)
.map { DecodedMessageWrapper.encode(it) }
// TODO: change xmtp-android `listBatchMessages` to include `topic`
.map { DecodedMessageWrapper.encode(it, "") }
}

AsyncFunction("sendMessage") { clientAddress: String, conversationTopic: String, conversationID: String?, contentJson: String ->
AsyncFunction("sendMessage") { clientAddress: String, conversationTopic: String, contentJson: String ->
logV("sendMessage")
val conversation =
findConversation(
Expand All @@ -276,17 +274,28 @@ class XMTPModule : Module() {
)
}

AsyncFunction("createConversation") { clientAddress: String, peerAddress: String, conversationID: String? ->
logV("createConversation")
AsyncFunction("createConversation") { clientAddress: String, peerAddress: String, contextJson: String ->
logV("createConversation: $contextJson")
val client = clients[clientAddress] ?: throw XMTPException("No client")

val context = JsonParser.parseString(contextJson).asJsonObject
val conversation = client.conversations.newConversation(
peerAddress,
context = InvitationV1ContextBuilder.buildFromConversation(
conversationId = conversationID ?: "", metadata = mapOf()
conversationId = when {
context.has("conversationID") -> context.get("conversationID").asString
else -> ""
},
metadata = when {
context.has("metadata") -> {
val metadata = context.get("metadata").asJsonObject
metadata.entrySet().associate { (key, value) -> key to value.asString }
}

else -> mapOf()
},
)
)
ConversationWrapper.encode(ConversationWithClientAddress(client, conversation))
ConversationWrapper.encode(client, conversation)
}

Function("subscribeToConversations") { clientAddress: String ->
Expand All @@ -299,21 +308,19 @@ class XMTPModule : Module() {
subscribeToAllMessages(clientAddress = clientAddress)
}

AsyncFunction("subscribeToMessages") { clientAddress: String, topic: String, conversationID: String? ->
AsyncFunction("subscribeToMessages") { clientAddress: String, topic: String ->
logV("subscribeToMessages")
subscribeToMessages(
clientAddress = clientAddress,
topic = topic,
conversationId = conversationID
topic = topic
)
}

AsyncFunction("unsubscribeFromMessages") { clientAddress: String, topic: String, conversationID: String? ->
AsyncFunction("unsubscribeFromMessages") { clientAddress: String, topic: String ->
logV("unsubscribeFromMessages")
unsubscribeFromMessages(
clientAddress = clientAddress,
topic = topic,
conversationId = conversationID
topic = topic
)
}

Expand All @@ -333,9 +340,9 @@ class XMTPModule : Module() {
}
}

AsyncFunction("decodeMessage") { clientAddress: String, topic: String, encryptedMessage: String, conversationID: String? ->
AsyncFunction("decodeMessage") { clientAddress: String, topic: String, encryptedMessage: String ->
logV("decodeMessage")
val encryptedMessageData = Base64.decode(encryptedMessage, Base64.NO_WRAP)
val encryptedMessageData = Base64.decode(encryptedMessage, NO_WRAP)
val envelope = EnvelopeBuilder.buildFromString(topic, Date(), encryptedMessageData)
val conversation =
findConversation(
Expand All @@ -344,7 +351,7 @@ class XMTPModule : Module() {
)
?: throw XMTPException("no conversation found for $topic")
val decodedMessage = conversation.decode(envelope)
DecodedMessageWrapper.encode(decodedMessage)
DecodedMessageWrapper.encode(decodedMessage, topic)
}
}

Expand Down Expand Up @@ -375,16 +382,15 @@ class XMTPModule : Module() {
private fun subscribeToConversations(clientAddress: String) {
val client = clients[clientAddress] ?: throw XMTPException("No client")

subscriptions["conversations"]?.cancel()
subscriptions["conversations"] = CoroutineScope(Dispatchers.IO).launch {
try {
client!!.conversations.stream().collect { conversation ->
client.conversations.stream().collect { conversation ->
sendEvent(
"conversation",
mapOf(
"topic" to conversation.topic,
"peerAddress" to conversation.peerAddress,
"version" to if (conversation.version == Conversation.Version.V1) "v1" else "v2",
"conversationID" to conversation.conversationId
"clientAddress" to clientAddress,
"conversation" to ConversationWrapper.encodeToObj(client, conversation)
)
)
}
Expand All @@ -398,16 +404,16 @@ class XMTPModule : Module() {
private fun subscribeToAllMessages(clientAddress: String) {
val client = clients[clientAddress] ?: throw XMTPException("No client")

subscriptions["messages"]?.cancel()
subscriptions["messages"] = CoroutineScope(Dispatchers.IO).launch {
try {
client!!.conversations.streamAllMessages().collect { message ->
client.conversations.streamAllMessages().collect { message ->
sendEvent(
"message",
mapOf(
"id" to message.id,
"content" to message.body,
"senderAddress" to message.senderAddress,
"sent" to message.sent
"clientAddress" to clientAddress,
// TODO: change xmtp-android `streamAllMessages` to include `topic`
"message" to DecodedMessageWrapper.encodeMap(message, ""),
)
)
}
Expand All @@ -418,22 +424,22 @@ class XMTPModule : Module() {
}
}

private fun subscribeToMessages(clientAddress: String, topic: String, conversationId: String?) {
private fun subscribeToMessages(clientAddress: String, topic: String) {
val conversation =
findConversation(
clientAddress = clientAddress,
topic = topic
) ?: return
subscriptions[conversation.cacheKey(clientAddress)]?.cancel()
subscriptions[conversation.cacheKey(clientAddress)] =
CoroutineScope(Dispatchers.IO).launch {
try {
conversation.streamMessages().collect { message ->
sendEvent(
"message",
mapOf(
"topic" to conversation.topic,
"conversationID" to conversation.conversationId,
"messageJSON" to DecodedMessageWrapper.encode(message)
"clientAddress" to clientAddress,
"message" to DecodedMessageWrapper.encodeMap(message, topic),
)
)
}
Expand All @@ -447,7 +453,6 @@ class XMTPModule : Module() {
private fun unsubscribeFromMessages(
clientAddress: String,
topic: String,
conversationId: String?,
) {
val conversation =
findConversation(
Expand All @@ -459,7 +464,7 @@ class XMTPModule : Module() {

private fun logV(msg: String) {
if (isDebugEnabled) {
Log.v("XMTPModule", msg);
Log.v("XMTPModule", msg)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,34 @@ import com.google.gson.GsonBuilder
import org.xmtp.android.library.Client
import org.xmtp.android.library.Conversation

data class ConversationWithClientAddress(
var clientAddress: String,
var topic: String,
var peerAddress: String,
var version: String,
var conversationId: String?,
) {
constructor(client: Client, conversation: Conversation): this(
clientAddress = client.address,
topic = conversation.topic,
peerAddress = conversation.peerAddress,
version = if (conversation.version == Conversation.Version.V1) "v1" else "v2",
conversationId = conversation.conversationId)

}

class ConversationWrapper {

companion object {
fun encode(model: ConversationWithClientAddress): String {
val gson = GsonBuilder().create()
val conversation = mapOf(
"clientAddress" to model.clientAddress,
"topic" to model.topic,
"peerAddress" to model.peerAddress,
"version" to model.version,
"conversationID" to model.conversationId
fun encodeToObj(client: Client, conversation: Conversation): Map<String, Any> {
val context = when (conversation.version) {
Conversation.Version.V2 -> mapOf<String, Any>(
"conversationID" to (conversation.conversationId ?: ""),
// TODO: expose the context/metadata explicitly in xmtp-android
"metadata" to conversation.toTopicData().invitation.context.metadataMap,
)

else -> mapOf()
}
return mapOf(
"clientAddress" to client.address,
"createdAt" to conversation.createdAt.time,
"context" to context,
"topic" to conversation.topic,
"peerAddress" to conversation.peerAddress,
"version" to if (conversation.version == Conversation.Version.V1) "v1" else "v2",
"conversationID" to (conversation.conversationId ?: "")
)
return gson.toJson(conversation)
}

fun encode(client: Client, conversation: Conversation): String {
val gson = GsonBuilder().create()
val obj = encodeToObj(client, conversation)
return gson.toJson(obj)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@ package expo.modules.xmtpreactnativesdk.wrappers

import com.google.gson.GsonBuilder
import org.xmtp.android.library.DecodedMessage
import org.xmtp.android.library.codecs.id

class DecodedMessageWrapper {

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

fun encodeMap(model: DecodedMessage): Map<String, Any> = mapOf(
fun encodeMap(model: DecodedMessage, topic: String): Map<String, Any> = mapOf(
"id" to model.id,
"topic" to topic,
"contentTypeId" to model.encodedContent.type.id,
"content" to ContentJson(model.encodedContent).toJsonMap(),
"senderAddress" to model.senderAddress,
"sent" to model.sent.getTime(),
"sent" to model.sent.time,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = expo.modules.xmtpreactnativesdk.example;
PRODUCT_NAME = "xmtpreactnativesdkexample";
PRODUCT_NAME = xmtpreactnativesdkexample;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand Down Expand Up @@ -451,7 +451,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = expo.modules.xmtpreactnativesdk.example;
PRODUCT_NAME = "xmtpreactnativesdkexample";
PRODUCT_NAME = xmtpreactnativesdkexample;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
Expand Down
2 changes: 1 addition & 1 deletion example/src/TestScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export default function TestScreen(): JSX.Element {
accessibilityLabel="tests-complete"
style={{ paddingHorizontal: 12 }}
>
{tests.slice(0, completedTests + 1).map((test: Test, i) => {
{(tests || []).slice(0, completedTests + 1).map((test: Test, i) => {
return (
<TestView
test={test}
Expand Down
Loading

0 comments on commit 630af81

Please sign in to comment.