Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
… kele/static-can-message
  • Loading branch information
nplasterer committed Dec 2, 2023
2 parents 2f6b7e1 + 123a9aa commit 51ee636
Show file tree
Hide file tree
Showing 57 changed files with 20,183 additions and 16,837 deletions.
10 changes: 7 additions & 3 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
module.exports = {
root: true,
extends: ["universe/native", "universe/web"],
ignorePatterns: ["build"],
};
extends: ['universe/native', 'universe/web'],
ignorePatterns: ['build'],
plugins: ['prettier'],
globals: {
__dirname: true,
},
}
13 changes: 13 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: Lint
on:
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-node@v3
- run: npm install
- run: npm run eslint
7 changes: 7 additions & 0 deletions .prettierrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
semi: false,
singleQuote: true,
trailingComma: 'es5',
arrowParens: 'always',
printWidth: 80,
}
1 change: 1 addition & 0 deletions .swiftformat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--indent tab
54 changes: 36 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Your app must use Android `minSdkVersion = 22` to work with the `xmtp-react-nati
The [XMTP message API](https://xmtp.org/docs/concepts/architectural-overview#network-layer) revolves around a network client that allows retrieving and sending messages to other network participants. A client must be connected to a wallet on startup. If this is the very first time the client is created, the client will generate a [key bundle](https://xmtp.org/docs/concepts/key-generation-and-usage) that is used to [encrypt and authenticate messages](https://xmtp.org/docs/concepts/invitation-and-message-encryption). The key bundle persists encrypted in the network using a [wallet signature](https://xmtp.org/docs/concepts/account-signatures). The public side of the key bundle is also regularly advertised on the network to allow parties to establish shared encryption keys. All this happens transparently, without requiring any additional code.

```tsx
import { Client } from '@xmtp/xmtp-react-native'
import { Client } from '@xmtp/react-native-sdk'
import { ConnectWallet, useSigner } from "@thirdweb-dev/react-native";

// Create the client with your wallet. This will connect to the XMTP development network by default
Expand Down Expand Up @@ -105,7 +105,7 @@ A client is created with `Client.create(wallet: Signer): Promise<Client>` that r
> The client connects to the XMTP `dev` environment by default. [Use `ClientOptions`](#configure-the-client) to change this and other parameters of the network connection.
```tsx
import { Client } from '@xmtp/xmtp-react-native'
import { Client } from '@xmtp/react-native-sdk'
// Create the client with a `Signer` from your application
const xmtp = await Client.create(wallet)
```
Expand All @@ -118,13 +118,14 @@ The client's network connection and key storage method can be configured with th
| ------------------------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| appVersion | `undefined` | Add a client app version identifier that's included with API requests.<br/>For example, you can use the following format: `appVersion: APP_NAME + '/' + APP_VERSION`.<br/>Setting this value provides telemetry that shows which apps are using the XMTP client SDK. This information can help XMTP developers provide app support, especially around communicating important SDK updates, including deprecations and required upgrades. |
| env | `dev` | Connect to the specified XMTP network environment. Valid values include `dev`, `production`, or `local`. For important details about working with these environments, see [XMTP `production` and `dev` network environments](#xmtp-production-and-dev-network-environments). |
| codecs | `[new XMTP.ReactionCodec()]` | Add codecs to support additional content types. |

## Handle conversations

Most of the time, when interacting with the network, you'll want to do it through `conversations`. Conversations are between two wallets.

```tsx
import { Client } from '@xmtp/xmtp-react-native'
import { Client } from '@xmtp/react-native-sdk'
// Create the client with a `Signer` from your application
const xmtp = await Client.create(wallet)
const conversations = xmtp.conversations
Expand Down Expand Up @@ -272,7 +273,7 @@ To learn more, see [Request and respect user consent](https://xmtp.org/docs/buil
If you would like to check and see if a blockchain address is registered on the network before instantiating a client instance, you can use `Client.canMessage`.

```tsx
import { Client } from '@xmtp/xmtp-react-native'
import { Client } from '@xmtp/react-native-sdk'

const isOnDevNetwork = await Client.canMessage(
'0x3F11b27F323b62B159D2642964fa27C46C841897'
Expand All @@ -292,7 +293,7 @@ For example:

```tsx
const ethers = require('ethers')
const { Client } = require('@xmtp/xmtp-react-native')
const { Client } = require('@xmtp/react-native-sdk')

async function main() {
//Create a random wallet for example purposes. On the frontend you should replace it with the user's wallet (metamask, rainbow, etc)
Expand Down Expand Up @@ -327,31 +328,48 @@ All send functions support `SendOptions` as an optional parameter. The `contentT

To learn more about content types, see [Content types with XMTP](https://xmtp.org/docs/concepts/content-types).

The SDK preregisters the following codecs:
Support for other types of content can be added by registering additional `ContentCodecs` with the `Client`. Every codec is associated with a content type identifier, `ContentTypeId`, which is used to signal to the client which codec should be used to process the content that is being sent or received.
For example, see the [Native Codecs](https://github.com/xmtp/xmtp-react-native/tree/main/src/lib/NativeCodecs) available in `xmtp-react-native`.

- For [Android](https://github.com/xmtp/xmtp-react-native/blob/main/android/src/main/java/expo/modules/xmtpreactnativesdk/wrappers/ContentJson.kt#L43-L53), using these [source codecs](https://github.com/xmtp/xmtp-android/tree/main/library/src/main/java/org/xmtp/android/library/codecs).
```ts
// Assuming we've loaded a fictional NumberCodec that can be used to encode numbers,
// and is identified with ContentTypeNumber, we can use it as follows.

- For [iOS](https://github.com/xmtp/xmtp-react-native/blob/main/ios/Wrappers/DecodedMessageWrapper.swift#L35-L48), using these [source codecs](https://github.com/xmtp/xmtp-ios/tree/main/Sources/XMTP/Codecs).
xmtp.register(new NumberCodec())
conversation.send(3.14, {
contentType: ContentTypeNumber
})
```

```tsx
await conversation.send({
reaction: {
reference: otherMessage.id,
action: "added",
schema: "unicode",
content: "💖",
},
});
Additional codecs can be configured through the `ClientOptions` parameter of `Client.create`. The `codecs` option is a list of codec instances that should be added to the default set of codecs (currently only the `TextCodec`). If a codec is added for a content type that is already in the default set, it will replace the original codec.

```ts
// Adding support for `xmtp.org/reaction` content type
import { ReactionCodec } from '@xmtp/react-native-sdk'
const xmtp = Client.create(wallet, { codecs: [new ReactionCodec()] })

await conversation.send({
reaction: {
reference: otherMessage.id,
action: "added",
schema: "unicode",
content: "💖",
},
});
```

To learn more about how to build a custom content type, see [Build a custom content type](https://xmtp.org/docs/content-types/introduction#create-custom-content-types).

Custom codecs and content types may be proposed as interoperable standards through XRCs. To learn about the custom content type proposal process, see [XIP-5](https://github.com/xmtp/XIPs/blob/main/XIPs/xip-5-message-content-types.md).

## Manually handle private key storage

The SDK will handle key storage for the user by encrypting the private key bundle using a signature generated from the wallet, and storing the encrypted payload on the XMTP network. This can be awkward for some server-side applications, where you may only want to give the application access to the XMTP keys but not your wallet keys. Mobile applications may also want to store keys in a secure enclave rather than rely on decrypting the remote keys on the network each time the application starts up.

You can export the unencrypted key bundle using the static method `Client.exportKeyBundle`, save it somewhere secure, and then provide those keys at a later time to initialize a new client using the exported XMTP identity.

```js
import { Client } from '@xmtp/xmtp-react-native'
import { Client } from '@xmtp/react-native-sdk'
// Get the keys using a valid Signer. Save them somewhere secure.
const keys = await Client.exportKeyBundle()
// Create a client using keys returned from getKeys
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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.16"
implementation "org.xmtp:android:0.6.17"
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
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,27 @@ class XMTPModule : Module() {
).toJson()
}

AsyncFunction("sendEncodedContent") { clientAddress: String, topic: String, encodedContentData: List<Int> ->
val conversation =
findConversation(
clientAddress = clientAddress,
topic = topic
) ?: throw XMTPException("no conversation found for $topic")

val encodedContentDataBytes =
encodedContentData.foldIndexed(ByteArray(encodedContentData.size)) { i, a, v ->
a.apply {
set(
i,
v.toByte()
)
}
}
val encodedContent = EncodedContent.parseFrom(encodedContentDataBytes)

conversation.send(encodedContent = encodedContent)
}

AsyncFunction("listConversations") { clientAddress: String ->
logV("listConversations")
val client = clients[clientAddress] ?: throw XMTPException("No client")
Expand All @@ -298,7 +319,7 @@ class XMTPModule : Module() {
val beforeDate = if (before != null) Date(before) else null
val afterDate = if (after != null) Date(after) else null

conversation.messages(
conversation.decryptedMessages(
limit = limit,
before = beforeDate,
after = afterDate,
Expand Down Expand Up @@ -350,7 +371,7 @@ class XMTPModule : Module() {
topicsList.add(Pair(topic, page))
}

client.conversations.listBatchMessages(topicsList)
client.conversations.listBatchDecryptedMessages(topicsList)
.map { DecodedMessageWrapper.encode(it) }
}

Expand Down Expand Up @@ -496,7 +517,7 @@ class XMTPModule : Module() {
topic = topic
)
?: throw XMTPException("no conversation found for $topic")
val decodedMessage = conversation.decode(envelope)
val decodedMessage = conversation.decrypt(envelope)
DecodedMessageWrapper.encode(decodedMessage)
}

Expand Down Expand Up @@ -591,7 +612,7 @@ class XMTPModule : Module() {
subscriptions[getMessagesKey(clientAddress)]?.cancel()
subscriptions[getMessagesKey(clientAddress)] = CoroutineScope(Dispatchers.IO).launch {
try {
client.conversations.streamAllMessages().collect { message ->
client.conversations.streamAllDecryptedMessages().collect { message ->
sendEvent(
"message",
mapOf(
Expand All @@ -617,7 +638,7 @@ class XMTPModule : Module() {
subscriptions[conversation.cacheKey(clientAddress)] =
CoroutineScope(Dispatchers.IO).launch {
try {
conversation.streamMessages().collect { message ->
conversation.streamDecryptedMessages().collect { message ->
sendEvent(
"message",
mapOf(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package expo.modules.xmtpreactnativesdk.wrappers

import android.util.Base64
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.google.protobuf.ByteString
Expand All @@ -14,6 +15,7 @@ import org.xmtp.android.library.codecs.ContentTypeReadReceipt
import org.xmtp.android.library.codecs.ContentTypeRemoteAttachment
import org.xmtp.android.library.codecs.ContentTypeReply
import org.xmtp.android.library.codecs.ContentTypeText
import org.xmtp.android.library.codecs.EncodedContent
import org.xmtp.android.library.codecs.Reaction
import org.xmtp.android.library.codecs.ReactionCodec
import org.xmtp.android.library.codecs.ReadReceipt
Expand All @@ -28,16 +30,17 @@ import org.xmtp.android.library.codecs.description
import org.xmtp.android.library.codecs.getReactionAction
import org.xmtp.android.library.codecs.getReactionSchema
import org.xmtp.android.library.codecs.id
import org.xmtp.proto.message.contents.Content.EncodedContent
import java.net.URL

class ContentJson(
val type: ContentTypeId,
val content: Any?,
private val encodedContent: EncodedContent? = null,
) {
constructor(encoded: EncodedContent) : this(
type = encoded.type,
content = encoded.decoded(),
encodedContent = encoded
);

companion object {
Expand Down Expand Up @@ -157,19 +160,44 @@ class ContentJson(
ContentTypeReply.id -> mapOf(
"reply" to mapOf(
"reference" to (content as Reply).reference,
"content" to ContentJson(content.contentType, content.content).toJsonMap(),
"content" to ContentJson(
content.contentType,
content.content,
encodedContent
).toJsonMap(),
"contentType" to content.contentType.description
)
)

ContentTypeReadReceipt.id -> mapOf(
"readReceipt" to ""
)

else -> mapOf(
"unknown" to mapOf(
"contentTypeId" to type.description
)
)
else -> {
val json = JsonObject()
encodedContent?.let {
val typeJson = JsonObject()
typeJson.addProperty("authorityId", encodedContent.type.authorityId)
typeJson.addProperty("typeId", encodedContent.type.typeId)
typeJson.addProperty("versionMajor", encodedContent.type.versionMajor)
typeJson.addProperty("versionMinor", encodedContent.type.versionMinor)
val parameters = GsonBuilder().create().toJson(encodedContent.parametersMap)

json.addProperty("fallback", encodedContent.fallback)
json.add("parameters", JsonParser.parseString(parameters))
json.add("type", typeJson)
}
val encodedContentJSON = json.toString()
if (encodedContentJSON.isNotBlank()) {
mapOf("encoded" to encodedContentJSON)
} else {
mapOf(
"unknown" to mapOf(
"contentTypeId" to type.description
)
)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
package expo.modules.xmtpreactnativesdk.wrappers

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

class DecodedMessageWrapper {

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

fun encodeMap(model: DecodedMessage): Map<String, Any> = mapOf(
fun encodeMap(model: DecryptedMessage): Map<String, Any> = mapOf(
"id" to model.id,
"topic" to model.topic,
"contentTypeId" to model.encodedContent.type.description,
"content" to ContentJson(model.encodedContent).toJsonMap(),
"senderAddress" to model.senderAddress,
"sent" to model.sent.time,
"fallback" to model.fallbackContent
"sent" to model.sentAt.time,
"fallback" to model.encodedContent.fallback
)
}
}
Loading

0 comments on commit 51ee636

Please sign in to comment.