Support attachments in your app built with XMTP
Use the attachment and remote attachment content types to support attachments in your app. Remote attachments of any size can be sent using the RemoteAttachmentCodec
and a storage provider.
You're welcome to provide feedback by commenting on the Remote Attachment Content Type Proposal XIP.
Configure the content type
In some SDKs, the AttachmentCodec
is already included in the SDK. If not, you can install the package using the following command:
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
In the JavaScript SDK, you need to import this package first.
npm i @xmtp/content-type-remote-attachment
After importing the package, you can register the codec.
import {
ContentTypeAttachment,
AttachmentCodec,
RemoteAttachmentCodec,
ContentTypeRemoteAttachment,
} from "@xmtp/content-type-remote-attachment";
// Create the XMTP client
const xmtp = await Client.create(signer, { env: "dev" });
xmtp.registerCodec(new AttachmentCodec());
xmtp.registerCodec(new RemoteAttachmentCodec());
The React SDK supports all current standards-track content types, but only text messages are enabled out of the box. Adding support for other standards-track content types requires a bit of configuration.
import {
XMTPProvider,
attachmentContentTypeConfig,
} from "@xmtp/react-sdk";
const contentTypeConfigs = [
attachmentContentTypeConfig,
];
createRoot(document.getElementById("root") as HTMLElement).render(
<StrictMode>
<XMTPProvider contentTypeConfigs={contentTypeConfigs}>
<App />
</XMTPProvider>
</StrictMode>,
);
import org.xmtp.android.library.codecs.Attachment
import org.xmtp.android.library.codecs.AttachmentCodec
import org.xmtp.android.library.codecs.ContentTypeAttachment
Client.register(codec = AttachmentCodec())
Client.register(codec = RemoteAttachmentCodec())
Client.register(AttachmentCodec());
Client.register(RemoteAttachmentCodec());
import 'package:xmtp/src/content/reply_codec.dart';
import 'package:xmtp/src/content/remote_attachment_codec.dart';
var registry = CodecRegistry()..registerCodec(RemoteAttachmentCodec());
const client = await Client.create(signer, {
env: "production",
codecs: [new RemoteAttachmentCodec(), new StaticAttachmentCodec()],
});
Send a remote attachment
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
Load the file. This example uses a web browser to load the file:
//image is the uploaded event.target.files[0];
const data = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
if (reader.result instanceof ArrayBuffer) {
resolve(reader.result);
} else {
reject(new Error("Not an ArrayBuffer"));
}
};
reader.readAsArrayBuffer(image);
});
Create an attachment object:
// Local file details
const attachment = {
filename: image?.name,
mimeType: image?.type,
data: new Uint8Array(data),
};
Use RemoteAttachmentCodec.encodeEncrypted
to encrypt an attachment:
const encryptedEncoded = await RemoteAttachmentCodec.encodeEncrypted(
attachment,
new AttachmentCodec(),
);
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 example app uses web3.storage. (This information is shared for educational purposes only and is not an endorsement.)
Now that you have a url
, you can create a RemoteAttachment
:
const remoteAttachment = {
url: url,
contentDigest: encryptedEncoded.digest,
salt: encryptedEncoded.salt,
nonce: encryptedEncoded.nonce,
secret: encryptedEncoded.secret,
scheme: "https://",
filename: attachment.filename,
contentLength: attachment.data.byteLength,
};
Now that you have a remote attachment, you can send it:
await conversation.send(remoteAttachment, {
contentType: ContentTypeRemoteAttachment,
});
Load the hooks and components you need:
import { useSendMessage } from "@xmtp/react-sdk";
import {
RemoteAttachmentCodec,
ContentTypeRemoteAttachment,
} from "@xmtp/content-type-remote-attachment";
// Inside your component...
const { sendMessage } = useSendMessage();
Load the file. This example uses a web browser to load the file:
//image is the uploaded event.target.files[0];
const data = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
if (reader.result instanceof ArrayBuffer) {
resolve(reader.result);
} else {
reject(new Error("Not an ArrayBuffer"));
}
};
reader.readAsArrayBuffer(image);
});
Create an attachment object:
// Local file details
const attachment = {
filename: image?.name,
mimeType: image?.type,
data: new Uint8Array(data),
};
Use RemoteAttachmentCodec.encodeEncrypted
to encrypt an attachment:
const encryptedEncoded = await RemoteAttachmentCodec.encodeEncrypted(
attachment,
new AttachmentCodec(),
);
Upload an encrypted attachment anywhere where it will be accessible via an HTTPS GET request. For example, you can use web3.storage or Thirdweb:
- Web3 Storage
- Thirdweb
const { Web3Storage } = require("web3.storage");
class Upload {
constructor(name, data) {
this.name = name;
this.data = data;
}
stream() {
const self = this;
return new ReadableStream({
start(controller) {
controller.enqueue(Buffer.from(self.data));
controller.close();
},
});
}
}
const upload = new Upload("uploadIdOfYourChoice", encryptedEncoded.payload);
const web3Storage = new Web3Storage({
token: "YOURTOKENHERE",
});
const cid = await web3Storage.put([upload]);
const url = `https://${cid}.ipfs.w3s.link/uploadIdOfYourChoice`;
import { useStorageUpload } from "@thirdweb-dev/react";
const { mutateAsync: upload } = useStorageUpload();
const uploadUrl = await upload({
//encryptedEncoded.payload.buffer is a Uint8Array
//We need to convert it to a File to upload it to the IPFS network
data: [new File([encryptedEncoded.payload.buffer], file.name)], // Convert Uint8Array back to File
options: { uploadWithGatewayUrl: true, uploadWithoutDirectory: true },
});
const url = uploadUrl[0];
Now that you have a url
, you can create a RemoteAttachment
:
const remoteAttachment = {
url: url,
contentDigest: encryptedEncoded.digest,
salt: encryptedEncoded.salt,
nonce: encryptedEncoded.nonce,
secret: encryptedEncoded.secret,
scheme: "https://",
filename: attachment.filename,
contentLength: attachment.data.byteLength,
};
Now that you have a remote attachment, you can send it:
await sendMessage(conversation, remoteAttachment, ContentTypeRemoteAttachment);
Create an attachment object:
val attachment = Attachment(
filename = "test.txt",
mimeType = "text/plain",
data = "hello world".toByteStringUtf8(),
)
Encode and encrypt an attachment for transport:
val encodedEncryptedContent = RemoteAttachment.encodeEncrypted(
content = attachment,
codec = AttachmentCodec(),
)
Create a remote attachment from an attachment:
val remoteAttachment = RemoteAttachment.from(
encryptedEncodedContent = encodedEncryptedContent
)
remoteAttachment.contentLength = attachment.data.size()
remoteAttachment.filename = attachment.filename
Send a remote attachment and set the contentType
:
val newConversation = client.conversations.newConversation(walletAddress)
newConversation.send(
content = remoteAttachment,
options = SendOptions(contentType = ContentTypeRemoteAttachment),
)
Create an attachment object:
let attachment = Attachment(
filename: "screenshot.png",
mimeType: "image/png",
data: Data(somePNGData)
)
Encode and encrypt an attachment for transport:
// Encode an attachment and encrypt the encoded content
const encryptedAttachment = try RemoteAttachment.encodeEncrypted(
content: attachment,
codec: AttachmentCodec()
)
Upload an encrypted attachment anywhere where it will be accessible via an HTTPS GET request. For example, you can use web3.storage:
func upload(data: Data, token: String): String {
let url = URL(string: "https://api.web3.storage/upload")!
var request = URLRequest(url: url)
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
request.addValue("XMTP", forHTTPHeaderField: "X-NAME")
request.httpMethod = "POST"
let responseData = try await URLSession.shared.upload(for: request, from: data).0
let response = try JSONDecoder().decode(Web3Storage.Response.self, from: responseData)
return "https://\(response.cid).ipfs.w3s.link"
}
let url = upload(data: encryptedAttachment.payload, token: YOUR_WEB3_STORAGE_TOKEN)
Create a remote attachment from an attachment:
let remoteAttachment = try RemoteAttachment(
url: url,
encryptedEncodedContent: encryptedEncodedContent
)
Send a remote attachment and set the contentType
:
try await conversation.send(
content: remoteAttachment,
options: .init(
contentType: ContentTypeRemoteAttachment,
contentFallback: "a description of the image"
)
)
// Initiating a new conversation between Alice and Bob using Bob's hex address
var convo = await alice.newConversation(bob.address.hex);
// Sending an initial message in the conversation
await alice.sendMessage(convo, "Here's an attachment for you:");
// Creating an attachment with the filename, MIME type, and content
var attachment = Attachment("foo.txt", "text/plain", utf8.encode("bar"));
// Uploading the attachment and getting a remote reference
var remote = await alice.upload(attachment, files.upload);
// Sending the remote attachment in the conversation with the specified content type
await alice.sendMessage(convo, remote, contentType: contentTypeRemoteAttachment);
This method takes a DecryptedLocalAttachment
object as an argument:
const { encryptedLocalFileUri, metadata } = await alice.encryptAttachment({
fileUri: `file://${file}`,
mimeType: "text/plain",
});
Upload an encrypted file to a remote server:
let url = await uploadFile(encryptedLocalFileUri);
Send a remote attachment message:
await convo.send({
remoteAttachment: {
...metadata,
scheme: "https://",
url,
},
});
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:
- JavaScript
- React
- Kotlin
- Swift
- Dart
- React Native
import { ContentTypeRemoteAttachment } from "@xmtp/content-type-remote-attachment";
if (message.contentType.sameAs(RemoteAttachmentContentType)) {
const attachment = await RemoteAttachmentCodec.load(message.content, client);
}
You now have the original attachment:
attachment.filename // => "screenshot.png"
attachment.mimeType // => "image/png",
attachment.data // => [the PNG data]
Once you've created the attachment object, you can create a preview to show in the message input field before sending:
const objectURL = URL.createObjectURL(
new Blob([Buffer.from(attachment.data)], {
type: attachment.mimeType,
}),
);
const img = document.createElement("img");
img.src = objectURL;
img.title = attachment.filename;
import { ContentTypeRemoteAttachment } from "@xmtp/content-type-remote-attachment";
const contentType = ContentTypeId.fromString(message.contentType);
if (contentType.sameAs(ContentTypeRemoteAttachment)) {
// The message is a RemoteAttachment
const attachment = await RemoteAttachmentCodec.load(message.content, client);
}
You now have the original attachment:
attachment.filename // => "screenshot.png"
attachment.mimeType // => "image/png",
attachment.data // => [the PNG data]
Once you've created the attachment object, you can create a preview to show in the message input field before sending:
const objectURL = URL.createObjectURL(
new Blob([Buffer.from(attachment.data)], {
type: attachment.mimeType,
}),
);
const img = document.createElement("img");
img.src = objectURL;
img.title = attachment.filename;
val message = newConversation.messages().first()
val loadedRemoteAttachment: RemoteAttachment = messages.content()
loadedRemoteAttachment.fetcher = Fetcher()
runBlocking {
val attachment: Attachment = loadedRemoteAttachment.load()
}
let attachment: Attachment = try await remoteAttachment.content()
You now have the original attachment:
attachment.filename // => "screenshot.png"
attachment.mimeType // => "image/png",
attachment.data // => [the PNG data]
Once you've created the attachment object, you can create a preview to show in the message input field before sending:
import UIKIt
import SwiftUI
struct ContentView: View {
var body: some View {
Image(uiImage: UIImage(data: attachment.data))
}
}
// And he should be able to download the remote attachment.
var downloaded = await bob.download(
messages[0].content as RemoteAttachment,
downloader: files.download,
);
// Filename: downloadedAttachment.filename
// MIME Type: downloadedAttachment.mimeType
// Data: utf8.decode(downloadedAttachment.data)
On the receiving end, you can use the decryptAttachment
method to decrypt the downloaded file. This method takes an EncryptedLocalAttachment
object as an argument and returns a DecryptedLocalAttachment
object.
if (message.contentTypeId === "xmtp.org/remoteStaticAttachment:1.0") {
// Now we can decrypt the downloaded file using the message metadata.
const attached = await xmtp_client.decryptAttachment({
encryptedLocalFileUri: downloadedFileUri,
metadata: message.content() as RemoteAttachmentContent,
})
//attached.filename
//attached.mimeType
//attached.fileUri
}
Display the attachment:
<Image source={{ uri: attached.fileUri }} />
To handle unsupported content types, refer to the fallback section.