From 27cad2e29179b042422b3e5f9d6b6ad1e810d340 Mon Sep 17 00:00:00 2001
From: Jennifer Hasegawa <5481259+jhaaaa@users.noreply.github.com>
Date: Thu, 20 Feb 2025 13:56:52 -0800
Subject: [PATCH 1/2] first pass
---
.../inboxes/content-types/attachments.mdx | 237 +++++++++++++++++-
1 file changed, 228 insertions(+), 9 deletions(-)
diff --git a/docs/pages/inboxes/content-types/attachments.mdx b/docs/pages/inboxes/content-types/attachments.mdx
index 6dd4fcd..38cd989 100644
--- a/docs/pages/inboxes/content-types/attachments.mdx
+++ b/docs/pages/inboxes/content-types/attachments.mdx
@@ -1,27 +1,31 @@
---
-description: Learn how to use the attachment and remote attachment content types to support attachments in your app built with XMTP
+description: Learn how to use the remote attachment, multiple remote attachment, or attachment content types to support attachments in your app built with XMTP
---
# Support attachments in your app built with XMTP
-Use the remote attachment or attachment content type to support attachments in your app.
+Use the remote attachment, multiple remote attachments, or attachment content type to support attachments in your app.
-- Use the [remote attachment content type](#support-remote-attachments-of-any-size) to send attachments of any size.
+- Use the [remote attachment content type](#support-remote-attachments-of-any-size) to send one remote attachment of any size.
+
+- Use the [multiple remote attachments content type](#support-multiple-remote-attachments-of-any-size-in-a-message) to send multiple remote attachments of any size.
- Use the [attachment content type](#support-attachments-smaller-than-1mb) to send attachments smaller than 1MB.
## Support remote attachments of any size
-Remote attachments of any size can be sent using the `RemoteAttachmentCodec` and a storage provider.
+One remote attachment of any size can be sent in a message using the `RemoteAttachmentCodec` and a storage provider.
+
+To send multiple remote attachments of any size in a single message, see [Support multiple remote attachments of any size in a message](#support-multiple-remote-attachments-of-any-size-in-a-message).
### Install the package
+In some SDKs, the `AttachmentCodec` is already included in the SDK. If not, you can install the package using the following command:
+
```bash
npm i @xmtp/content-type-remote-attachment
```
-> In some SDKs, the `AttachmentCodec` is already included in the SDK. If not, you can install the package using the following command:
-
### Configure the content type
After importing the package, you can register the codec.
@@ -110,7 +114,7 @@ const encryptedEncoded = await RemoteAttachmentCodec.encodeEncrypted(
);
```
-Upload an encrypted attachment to a location where it will be accessible via an HTTPS GET request. This location will depend on which storage provider you use based on your needs. For example, the [xmtp.chat](https://xmtp.chat/) example app uses web3.storage. (This information is shared for educational purposes only and is not an endorsement.)
+Upload an encrypted attachment to a location where it will be accessible via an HTTPS GET request. This location will depend on which storage provider you use based on your needs.
Now that you have a `url`, you can create a `RemoteAttachment`:
@@ -280,7 +284,7 @@ try await conversation.send(
### Receive, decode, and decrypt a remote attachment
-Now that you can receive a remote attachment, you need a way to receive a remote attachment. For example:
+Now that you can send a remote attachment, you need a way to receive it. For example:
:::code-group
@@ -386,6 +390,221 @@ struct ContentView: View {
To handle unsupported content types, refer to the [fallback](/inboxes/build-inbox/#handle-unsupported-content-types) section.
+## Support multiple remote attachments of any size
+
+Multiple remote attachments of any size can be sent in a single message using the `MultiRemoteAttachmentCodec` and a storage provider.
+
+### Create multiple attachment objects
+
+Each attachment in the attachments array contains a URL that points to an encrypted `EncodedContent` object. The content must be accessible by an HTTP `GET` request to the URL.
+
+:::code-group
+
+```ts [Browser]
+code sample needed
+```
+
+```ts [Node]
+code sample needed
+```
+
+```ts [React Native]
+const attachment1: DecryptedLocalAttachment = {
+ fileUri: "content://media/external/images/media/image-1.png",
+ mimeType: "image/png",
+ filename: "image-1.png"
+}
+
+const attachment2: DecryptedLocalAttachment = {
+ fileUri: "content://media/external/images/media/image-2.png",
+ mimeType: "image/png",
+ filename: "image-2.png"
+}
+```
+
+```kotlin [Kotlin]
+code sample needed
+```
+
+```swift [Swift]
+code sample needed
+```
+
+:::
+
+### Encrypt and upload multiple attachments to a remote server
+
+:::code-group
+
+```ts [Browser]
+code sample needed
+```
+
+```ts [Node]
+code sample needed
+```
+
+```ts [React Native]
+const remoteAttachments: RemoteAttachmentInfo[] = []
+ for (const attachment of [attachment1, attachment2]) {
+ // Encrypt the attachment and receive the local URI of the encrypted file
+ const { encryptedLocalFileUri, metadata } = await alix.encryptAttachment(attachment)
+
+ // Upload the attachment to a remote server and receive the URL
+ const url = testUploadAttachmentForUrl(encryptedLocalFileUri)
+
+ // Build the remote attachment info
+ const remoteAttachmentInfo =
+ MultiRemoteAttachmentCodec.buildMultiRemoteAttachmentInfo(url, metadata)
+ remoteAttachments.push(remoteAttachmentInfo)
+ }
+```
+
+```kotlin [Kotlin]
+code sample needed
+```
+
+```swift [Swift]
+code sample needed
+```
+
+:::
+
+### Send a message with multiple remote attachments
+
+:::code-group
+
+```ts [Browser]
+code sample needed
+```
+
+```ts [Node]
+code sample needed
+```
+
+```ts [React Native]
+await convo.send({
+ multiRemoteAttachment: {
+ attachments: remoteAttachments,
+ },
+ })
+```
+
+```kotlin [Kotlin]
+code sample needed
+```
+
+```swift [Swift]
+code sample needed
+```
+
+:::
+
+### Decode a message with multiple remote attachments
+
+:::code-group
+
+```ts [Browser]
+code sample needed
+```
+
+```ts [Node]
+code sample needed
+```
+
+```ts [React Native]
+const message = (await group.messages()).first()
+if (message.contentTypeId == 'xmtp.org/multiRemoteStaticContent:1.0') {
+ const multiRemoteAttachment: MultiRemoteAttachment = message.content()
+}
+```
+
+```kotlin [Kotlin]
+code sample needed
+```
+
+```swift [Swift]
+code sample needed
+```
+
+:::
+
+### Download and decrypt the attachments
+
+:::code-group
+
+```ts [Browser]
+code sample needed
+```
+
+```ts [Node]
+code sample needed
+```
+
+```ts [React Native]
+const decryptedAttachments: DecryptedLocalAttachment[] = []
+for (const attachment of multiRemoteAttachment.attachments) {
+ // Downloading the encrypted payload from the attachment URL and save the local file
+ const encryptedFileLocalURIAfterDownload: string = downloadFromUrlForLocalUri(
+ attachment.url
+ )
+ // Decrypt the local file
+ const decryptedLocalAttachment = await alix.decryptAttachment({
+ encryptedLocalFileUri: encryptedFileLocalURIAfterDownload,
+ metadata: {
+ secret: attachment.secret,
+ salt: attachment.salt,
+ nonce: attachment.nonce,
+ contentDigest: attachment.contentDigest,
+ filename: attachment.filename,
+ } as RemoteAttachmentContent,
+ })
+ decryptedAttachments.push(decryptedLocalAttachment)
+ }
+```
+
+```kotlin [Kotlin]
+code sample needed
+```
+
+```swift [Swift]
+code sample needed
+```
+
+:::
+
+### Display the attachments
+
+Use the file URIs in the decrypted attachments objects to display the attachments.
+
+:::code-group
+
+```ts [Browser]
+code sample needed
+```
+
+```ts [Node]
+code sample needed
+```
+
+```ts [React Native]
+const attachment1 = decryptedAttachments[0]
+const attachment2 = decryptedAttachments[1]
+
+
+
+```
+
+```kotlin [Kotlin]
+code sample needed
+```
+
+```swift [Swift]
+code sample needed
+```
+
+:::
+
## Support attachments smaller than 1MB
Attachments smaller than 1MB can be sent using the `AttachmentCodec`. The codec will automatically encrypt the attachment and upload it to the XMTP network.
@@ -475,4 +694,4 @@ if (message.contentType.sameAs(ContentTypeAttachment)) {
});
const url = URL.createObjectURL(blobdecoded);
}
-```
\ No newline at end of file
+```
From 90d07a108a77d4dc95a0b5263b785c17cf068578 Mon Sep 17 00:00:00 2001
From: Jennifer Hasegawa <5481259+jhaaaa@users.noreply.github.com>
Date: Wed, 26 Feb 2025 17:42:11 -0800
Subject: [PATCH 2/2] add code samples for kotlin, swift
---
.../inboxes/content-types/attachments.mdx | 161 ++++++++++++++++--
1 file changed, 149 insertions(+), 12 deletions(-)
diff --git a/docs/pages/inboxes/content-types/attachments.mdx b/docs/pages/inboxes/content-types/attachments.mdx
index 38cd989..9686796 100644
--- a/docs/pages/inboxes/content-types/attachments.mdx
+++ b/docs/pages/inboxes/content-types/attachments.mdx
@@ -394,6 +394,32 @@ To handle unsupported content types, refer to the [fallback](/inboxes/build-inbo
Multiple remote attachments of any size can be sent in a single message using the `MultiRemoteAttachmentCodec` and a storage provider.
+### Register necessary codecs
+
+:::code-group
+
+```tsx [React Native]
+export const registerCodecs = () => {
+ Client.register(new AttachmentCodec());
+ Client.register(new RemoteAttachmentCodec());
+ Client.register(new MultiRemoteAttachmentCodec());
+};
+```
+
+```kotin [Kotlin]
+Client.register(codec = AttachmentCodec())
+Client.register(codec = RemoteAttachmentCodec())
+Client.register(codec = MultiRemoteAttachmentCodec())
+```
+
+```swift [Swift]
+Client.register(codec: AttachmentCodec())
+Client.register(codec: RemoteAttachmentCodec())
+Client.register(codec: MultiRemoteAttachmentCodec())
+```
+
+:::
+
### Create multiple attachment objects
Each attachment in the attachments array contains a URL that points to an encrypted `EncodedContent` object. The content must be accessible by an HTTP `GET` request to the URL.
@@ -423,11 +449,31 @@ const attachment2: DecryptedLocalAttachment = {
```
```kotlin [Kotlin]
-code sample needed
+val attachment1 = Attachment(
+ filename = "test1.txt",
+ mimeType = "text/plain",
+ data = "hello world".toByteStringUtf8(),
+)
+
+val attachment2 = Attachment(
+ filename = "test2.txt",
+ mimeType = "text/plain",
+ data = "hello world".toByteStringUtf8(),
+)
```
```swift [Swift]
-code sample needed
+let attachment1 = Attachment(
+ filename: "test1.txt",
+ mimeType: "text/plain",
+ data: Data("hello world".utf8)
+)
+
+let attachment2 = Attachment(
+ filename: "test2.txt",
+ mimeType: "text/plain",
+ data: Data("hello world".utf8)
+)
```
:::
@@ -461,11 +507,35 @@ const remoteAttachments: RemoteAttachmentInfo[] = []
```
```kotlin [Kotlin]
-code sample needed
+val attachmentCodec = AttachmentCodec()
+val remoteAttachmentInfos: MutableList = ArrayList()
+
+for (attachment: Attachment in listOf(attachment1, attachment2)) {
+ val encodedBytes = attachmentCodec.encode(attachment).toByteArray()
+ val encryptedAttachment = MultiRemoteAttachmentCodec.encryptBytesForLocalAttachment(encodedBytes, attachment.filename)
+ val url = testUploadEncryptedPayload(encryptedAttachment.payload.toByteArray())
+ val remoteAttachmentInfo = MultiRemoteAttachmentCodec.buildRemoteAttachmentInfo(encryptedAttachment, URL(url))
+ remoteAttachmentInfos.add(remoteAttachmentInfo)
+}
```
```swift [Swift]
-code sample needed
+var remoteAttachmentInfos: [MultiRemoteAttachment.RemoteAttachmentInfo] = []
+
+for att in [attachment1, attachment2] {
+ // 1) Encode the attachment to raw bytes
+ let encodedBytes = try AttachmentCodec().encode(content: att).serializedData()
+ // 2) Encrypt the bytes locally
+ let encrypted = try MultiRemoteAttachmentCodec.encryptBytesForLocalAttachment(encodedBytes, filename: att.filename)
+ // 3) "Upload" it, and get a random https:// URL back
+ let urlString = fakeUpload(encrypted.payload)
+ // 4) Build a RemoteAttachmentInfo for that URL
+ let info = try MultiRemoteAttachmentCodec.buildRemoteAttachmentInfo(
+ encryptedAttachment: encrypted,
+ remoteUrl: URL(string: urlString)!
+ )
+ remoteAttachmentInfos.append(info)
+}
```
:::
@@ -491,11 +561,20 @@ await convo.send({
```
```kotlin [Kotlin]
-code sample needed
+val multiRemoteAttachment = MultiRemoteAttachment(remoteAttachments = remoteAttachmentInfos.toList())
+
+runBlocking {
+ aliceConversation.send(
+ content = multiRemoteAttachment,
+ options = SendOptions(contentType = ContentTypeMultiRemoteAttachment),
+ )
+}
```
```swift [Swift]
-code sample needed
+let multiRemoteAttachment = MultiRemoteAttachment(remoteAttachments: remoteAttachmentInfos)
+let encodedContent = try MultiRemoteAttachmentCodec().encode(content: multiRemoteAttachment)
+_ = try await alixConversation.send(encodedContent: encodedContent)
```
:::
@@ -520,11 +599,24 @@ if (message.contentTypeId == 'xmtp.org/multiRemoteStaticContent:1.0') {
```
```kotlin [Kotlin]
-code sample needed
+val loadedMultiRemoteAttachment: FfiMultiRemoteAttachment = messages[0].content()!!
+
+val remoteAttachments = loadedMultiRemoteAttachment.attachments.map { attachment ->
+ RemoteAttachment(
+ url = URL(attachment.url),
+ filename = attachment.filename,
+ contentDigest = attachment.contentDigest,
+ nonce = attachment.nonce.toByteString(),
+ scheme = attachment.scheme,
+ salt = attachment.salt.toByteString(),
+ secret = attachment.secret.toByteString(),
+ contentLength = attachment.contentLength?.toInt(),
+ )
+}
```
```swift [Swift]
-code sample needed
+let loadedMultiRemoteAttachment: MultiRemoteAttachment = try received.content()
```
:::
@@ -564,11 +656,51 @@ for (const attachment of multiRemoteAttachment.attachments) {
```
```kotlin [Kotlin]
-code sample needed
+val textAttachments: MutableList = ArrayList()
+
+for (remoteAttachment: RemoteAttachment in remoteAttachments) {
+ val url = remoteAttachment.url.toString()
+ // Simulate Download
+ val encryptedPayload: ByteArray = encryptedPayloadUrls[url]!!
+ // Combine encrypted payload with RemoteAttachmentInfo
+ val encryptedAttachment: EncryptedEncodedContent = MultiRemoteAttachmentCodec.buildEncryptAttachmentResult(remoteAttachment, encryptedPayload)
+ // Decrypt payload
+ val encodedContent: EncodedContent = MultiRemoteAttachmentCodec.decryptAttachment(encryptedAttachment)
+ // Convert EncodedContent to Attachment
+ val attachment = attachmentCodec.decode(encodedContent)
+ textAttachments.add(attachment)
+}
```
```swift [Swift]
-code sample needed
+var decodedAttachments: [Attachment] = []
+for info in loadedMultiRemoteAttachment.remoteAttachments {
+ let remoteAttachment = try RemoteAttachment(
+ url: info.url,
+ contentDigest: info.contentDigest,
+ secret: info.secret,
+ salt: info.salt,
+ nonce: info.nonce,
+ scheme: RemoteAttachment.Scheme(rawValue: info.scheme) ?? .https,
+ contentLength: Int(info.contentLength),
+ filename: info.filename
+ )
+
+ // Download the encrypted payload
+ guard let downloadedPayload = encryptedPayloads[remoteAttachment.url] else {
+ XCTFail("No stored payload for \(remoteAttachment.url)")
+ return
+ }
+
+ let encryptedAttachment = MultiRemoteAttachmentCodec.buildEncryptAttachmentResult(
+ remoteAttachment: remoteAttachment,
+ encryptedPayload: downloadedPayload
+ )
+
+ let decodedContent = try MultiRemoteAttachmentCodec.decryptAttachment(encryptedAttachment)
+ let finalAttachment: Attachment = try decodedContent.decoded()
+ decodedAttachments.append(finalAttachment)
+}
```
:::
@@ -596,11 +728,16 @@ const attachment2 = decryptedAttachments[1]
```
```kotlin [Kotlin]
-code sample needed
+ assertEquals(textAttachments[0].filename, "test1.txt")
+ assertEquals(textAttachments[1].filename, "test2.txt")
+} else {
+ AssertionError("expected a MultiRemoteAttachment message")
+}
```
```swift [Swift]
-code sample needed
+decodedAttachments[0].filename // "test1.txt"
+decodedAttachments[1].filename // "test2.txt"
```
:::