Remote Attachment Content Type
Remote attachments of any size can be sent using the RemoteAttachmentCodec
and a storage provider.
You are welcome to provide feedback on by commenting on the Remote Attachment Content Type XIP Proposal.
Configure content type
In some SDK's the AttachmentCodec
is already included in the SDK. If not, you can install the package using the following command:
- JavaScript
- React
- Swift
- Kotlin
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>,
);
Client.register(AttachmentCodec());
Client.register(RemoteAttachmentCodec());
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())
Send a remote attachment
- JavaScript
- Swift
- Kotlin
Load the file, in this example im using 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 the attachment object
// Local file details
const attachment = {
filename: image?.name,
mimeType: image?.type,
data: new Uint8Array(data),
};
Use the RemoteAttachmentCodec.encodeEncrypted
to encrypt the attachment:
const encryptedEncoded = await RemoteAttachmentCodec.encodeEncrypted(
attachment,
new AttachmentCodec(),
);
Upload the encrypted attachment anywhere where it will be accessible via an HTTPS GET request. For example, you can use web3.storage:
- 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];
Create a remote attachment. 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,
};
Send a remote attachment. Now that you have a remote attachment, you can send it:
await conversation.send(remoteAttachment, {
contentType: ContentTypeRemoteAttachment,
});
Create the attachment object
let attachment = Attachment(
filename: "screenshot.png",
mimeType: "image/png",
data: Data(somePNGData)
)
Encode and encrypt an attachment for transport
// Encode the attachment and encrypt that encoded content
const encryptedAttachment = try RemoteAttachment.encodeEncrypted(
content: attachment,
codec: AttachmentCodec()
)
Upload the 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"
)
)
Create the 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),
)
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
- Swift
- Kotlin
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 have the attachment object created, you can also create a preview for what to show in a message input 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;
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 have the attachment object created, you can also create a preview for what to show in a message input before sending:
import UIKIt
import SwiftUI
struct ContentView: View {
var body: some View {
Image(uiImage: UIImage(data: attachment.data))
}
}
val message = newConversation.messages().first()
val loadedRemoteAttachment: RemoteAttachment = messages.content()
loadedRemoteAttachment.fetcher = Fetcher()
runBlocking {
val attachment: Attachment = loadedRemoteAttachment.load()
}