XMTP-iOS
xmtp-ios
为 iOS 应用提供了一个用于 XMTP 消息 API 客户端的 Swift 实现。
使用 xmtp-ios
来与 XMTP 集成,实现区块链账户之间发送消息,包括私密信息、通知、公告等。
为了跟踪最新的 SDK 发展,请查看此仓库中的 问题标签。
要了解 XMTP 的更多信息并获得常见问题解答,请参阅 XMTP 文档。
xmtp-ios
的快速入门和示例应用
使用 -
使用 XMTP iOS 快速入门应用作为构建 XMTP 应用程序的辅助工具。这个基于消息的简单应用具有故意非侵入式的用户界面,以帮助您更容易地进行构建。
-
使用 XMTP Inbox iOS 示例应用作为最佳实践实现功能的参考实现。
参考文档
查看参考
访问Swift客户端SDK参考文档。
使用SwiftPackageManager安装
使用Xcode将内容添加到项目中(文件>添加包...)或将以下内容添加到您的Package.swift
文件
.package(url: "https://github.com/xmtp/xmtp-ios", branch: "main")
使用说明概述
XMTP消息API围绕一个消息API客户端(客户端)展开,允许检索和发送消息到其他XMTP网络参与者。客户端在启动时必须连接到钱包应用。如果这是客户端第一次被创建,客户端将生成一个密钥包,用于加密和验证消息。密钥包使用账户签名加密后持久存储在网络中。密钥包的公共部分也会定期在网络上广播,以便各方可以建立共享加密密钥。所有这些都是透明发生的,无需任何额外的代码。
import XMTP
// You'll want to replace this with a wallet from your application.
let account = try PrivateKey.generate()
// Create the client with your wallet. This will connect to the XMTP `dev` network by default.
// The account is anything that conforms to the `XMTP.SigningKey` protocol.
let client = try await Client.create(account: account)
// Start a conversation with XMTP
let conversation = try await client.conversations.newConversation(with: "0x3F11b27F323b62B159D2642964fa27C46C841897")
// Load all messages in the conversation
let messages = try await conversation.messages()
// Send a message
try await conversation.send(content: "gm")
// Listen for new messages in the conversation
for try await message in conversation.streamMessages() {
print("\(message.senderAddress): \(message.body)")
}
创建客户端
使用 Client.create(account: SigningKey) async throws -> Client
创建客户端,需要一个能为你生成签名的对象。客户端将在两种情况下请求签名:
- 为了签署新生成的密钥集。这只有在密钥集在存储中找不到时才发生。
- 为了签署用于在存储中加密密钥集的随机盐。每次启动客户端时都会发生,包括第一次启动。
重要
客户端默认连接到 XMTP dev
环境。使用 ClientOptions
来更改此设置和其他网络连接参数。
import XMTP
// Create the client with a `SigningKey` from your app
let client = try await Client.create(account: account, options: .init(api: .init(env: .production)))
从已保存的密钥创建客户端
您可以通过客户端的 privateKeyBundle
属性保存您的密钥
// Create the client with a `SigningKey` from your app
let client = try await Client.create(account: account, options: .init(api: .init(env: .production)))
// Get the key bundle
let keys = client.privateKeyBundle
// Serialize the key bundle and store it somewhere safe
let keysData = try keys.serializedData()
一旦您有了这些密钥,您就可以使用 Client.from
创建一个新的客户端
let keys = try PrivateKeyBundle(serializedData: keysData)
let client = try Client.from(bundle: keys, options: .init(api: .init(env: .production)))
配置客户端
使用 Client.create
的这些可选参数,您可以配置客户端的网络连接和密钥存储方法
参数 | 默认 | 描述 |
---|---|---|
env | dev |
连接到指定的 XMTP 网络 environment。有效值包括 .dev ,.production 或 .local 。有关这些环境的详细说明,请参阅 XMTP production 和 dev 网络 environment。 |
env
配置 // Configure the client to use the `production` network
let clientOptions = ClientOptions(api: .init(env: .production))
let client = try await Client.create(account: account, options: clientOptions)
配置内容类型
您可以通过调用 Client.register
使用自定义内容类型。SDK 内包含两种常用内容类型编解码器,分别为 AttachmentCodec
和 RemoteAttachmentCodec
。
Client.register(AttachmentCodec())
Client.register(RemoteAttachmentCodec())
了解如何使用 AttachmentCodec
和 RemoteAttachmentCodec
的详细信息,请参阅处理不同内容类型。
处理对话
在与网络交互时,大多数情况下您会希望通过 conversations
进行。对话是在两个账户之间进行的。
import XMTP
// Create the client with a wallet from your app
let client = try await Client.create(account: account)
let conversations = try await client.conversations.list()
列出现有对话
您可以获取包含一条或多条消息的所有对话列表。
let allConversations = try await client.conversations.list()
for conversation in allConversations {
print("Saying GM to \(conversation.peerAddress)")
try await conversation.send(content: "gm")
}
这些对话包括用户的所有对话,不受创建谈话的应用程序影响。此功能提供了可互操作收件箱的概念,使用户能够在任何使用 XMTP 构建的应用程序中访问所有对话。
监听新对话
您还可以实时监听新对话的开始。这将允许应用程序显示来自新联系人的传入消息。
警告
此流将无限期继续。要结束流,请从循环中断开。
for try await conversation in client.conversations.stream() {
print("New conversation started with \(conversation.peerAddress)")
// Say hello to your new friend
try await conversation.send(content: "Hi there!")
// Break from the loop to stop listening
break
}
开始新的对话
您可以在XMTP网络上使用任何以太坊地址创建新的对话。
let newConversation = try await client.conversations.newConversation(with: "0x3F11b27F323b62B159D2642964fa27C46C841897")
发送消息
要发送消息,接收者必须至少创建过一次客户端,并在随后在网络中发布了他们的密钥集。消息使用账户地址进行标识。默认情况下,消息负载支持纯文本字符串。
有关其他内容类型支持的信息,请参见处理不同内容类型。
let conversation = try await client.conversations.newConversation(with: "0x3F11b27F323b62B159D2642964fa27C46C841897")
try await conversation.send(content: "Hello world")
列出对话中的消息
通过调用conversation.messages()
可以获取对话中的完整消息历史。
for conversation in client.conversations.list() {
let messagesInConversation = try await conversation.messages()
}
带有分页的列出对话中的消息
可能需要逐页检索和处理对话中的消息。您可以通过调用conversation.messages(limit: Int, before: Date)
完成此操作,该调用将返回在指定时间之前发送的指定数量的消息。
let conversation = try await client.conversations.newConversation(with: "0x3F11b27F323b62B159D2642964fa27C46C841897")
let messages = try await conversation.messages(limit: 25)
let nextPage = try await conversation.messages(limit: 25, before: messages[0].sent)
监听对话中的新消息
您可以通过调用conversation.streamMessages()
来监听对话中任何新的消息(传入或传出)。
成功接收的消息(在解码和解密过程中未抛出异常)可以认为是真实的。真实意味着它是由message.senderAddress
账户的所有者发送的,并且在传输过程中没有被修改。message.sent
时间戳可以认为是发送者设置的时间戳。
stream
方法返回的流是一个异步迭代器,因此它可以由for-await-of循环使用。然而请注意,由于其本质是无限的,因此与它一起使用的任何循环结构将不会终止,除非显式启动终止(通过退出循环)。
let conversation = try await client.conversations.newConversation(with: "0x3F11b27F323b62B159D2642964fa27C46C841897")
for try await message in conversation.streamMessages() {
if message.senderAddress == client.address {
// This message was sent from me
continue
}
print("New message from \(message.senderAddress): \(message.body)")
}
解码单个消息
您可以使用decode
方法从XMTP中解码单个Envelope
。
let conversation = try await client.conversations.newConversation(with: "0x3F11b27F323b62B159D2642964fa27C46C841897")
// Assume this function returns an Envelope that contains a message for the above conversation
let envelope = getEnvelopeFromXMTP()
let decodedMessage = try conversation.decode(envelope)
序列化和反序列化对话
您可以使用对话对象的encodedContainer
属性将其本地保存。这返回一个符合Codable
的ConversationContainer
对象。
// Get a conversation
let conversation = try await client.conversations.newConversation(with: "0x3F11b27F323b62B159D2642964fa27C46C841897")
// Get a container
let container = conversation.encodedContainer
// Dump it to JSON
let encoder = JSONEncoder()
let data = try encoder.encode(container)
// Get it back from JSON
let decoder = JSONDecoder()
let containerAgain = try decoder.decode(ConversationContainer.self, from: data)
// Get an actual Conversation object like we had above
let decodedConversation = containerAgain.decode(with: client)
try await decodedConversation.send(text: "hi")
处理不同内容类型
所有的发送函数都支持将 SendOptions
作为可选参数。选项 contentType
允许指定除默认字符串内容类型外的不同内容类型,该内容类型用内容类型标识符 ContentTypeText
来识别。
了解更多关于内容类型的信息,请参阅XMTP中的内容类型。
可以通过向客户端注册额外的 ContentCodec
来添加对其他内容类型的支持。每个编解码器都与一个内容类型标识符相关联,ContentTypeID
,它用于向客户端指示应该使用哪个编解码器来处理发送或接收的内容。
例如,参见在 xmtp-ios
中可用的 编解码器。
发送远程附件
使用RemoteAttachmentCodec包来启用你的应用程序发送和接收消息附件。
消息附件是文件。更具体地说,附件是具有以下对象的物体:
filename
大多数文件都有名字,至少是最常见的文件类型。mimeType
这是一个什么类型的文件?你通常可以根据文件扩展名来假设这一点,但有一个专门的字段会更好。请参阅常见MIME类型列表。data
这是什么文件的数据?大多数文件都有数据。如果文件没有数据,那么它可能不是最有意思的发送内容。
由于XMTP消息的大小不能超过1MB,我们需要将附件存储在XMTP网络之外的地方。换句话说,我们需要将其存储在远程位置。
端到端加密不仅适用于XMTP消息,也适用于消息附件。因此,在存储附件之前,我们需要加密它。
创建附件对象
let attachment = Attachment(
filename: "screenshot.png",
mimeType: "image/png",
data: Data(somePNGData)
)
加密附件
使用RemoteAttachmentCodec.encodeEncrypted
加密附件
// Encode the attachment and encrypt that encoded content
const encryptedAttachment = try RemoteAttachment.encodeEncrypted(
content: attachment,
codec: AttachmentCodec()
)
上传加密附件
将加密附件上传到可通过HTTPS GET请求访问的任何位置。例如,您可以使用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)
创建远程附件
现在您有了url
,您可以创建一个RemoteAttachment
。
let remoteAttachment = try RemoteAttachment(
url: url,
encryptedEncodedContent: encryptedEncodedContent
)
发送远程附件
现在您有了远程附件,您可以发送它
try await conversation.send(
content: remoteAttachment,
options: .init(
contentType: ContentTypeRemoteAttachment,
contentFallback: "a description of the image"
)
)
请注意,我们使用 contentFallback
来启用不支持这些内容类型的客户端仍能显示一些内容。对于客户端确实支持这些类型的情况,他们可以使用内容降级作为辅助功能用途的alt文本。
接收远程附件
现在您已能够发送远程附件,您还需要一种接收远程附件的方法。例如
let messages = try await conversation.messages()
let message = messages[0]
guard message.encodedContent.contentType == ContentTypeRemoteAttachment else {
return
}
const remoteAttachment: RemoteAttachment = try message.content()
下载、解密和解码附件
现在您已能够接收远程附件,您需要下载、解密和解码它,以便您的应用程序显示它。例如
let attachment: Attachment = try await remoteAttachment.content()
现在您有了原始附件
attachment.filename // => "screenshot.png"
attachment.mimeType // => "image/png",
attachment.data // => [the PNG data]
显示附件
按您的意愿在您的应用程序中显示附件。例如,您可以将它显示为图像
import UIKIt
import SwiftUI
struct ContentView: View {
var body: some View {
Image(uiImage: UIImage(data: attachment.data))
}
}
处理自定义内容类型
除了这个之外,自定义编解码器和内容类型可能会通过XRCs被提议作为可互操作的标准。要了解更多关于自定义内容类型提议过程的信息,请参阅 XIP-5。
压缩
消息内容可以使用压缩选项进行可选压缩。选项的值是要使用的压缩算法的名称。目前支持的是gzip和deflate。压缩应用于内容编解码器生成的字节。
内容将在接收端透明地解压缩。请注意,客户端强制执行最大内容大小。默认限制可以通过ClientOptions进行覆盖。因此,一个在接收端会超过该限制的消息将无法解码。
try await conversation.send(text: '#'.repeat(1000), options: .init(compression: .gzip))
🏗 破坏性修订
由于 xmtp-ios
正在积极开发,您应该预料到可能需要您采用最新SDK版本来确保您的应用程序按预期继续工作的破坏性修订。
XMTP在 XMTP Discord社区 中沟通破坏性修订,并尽可能提供提前通知。此外,xmtp-ios
发布中的破坏性修订在 发行版页面 上有描述。
弃用
SDK的旧版本最终将被弃用,这意味着
- 网络将不支持并最终主动拒绝使用已弃用版本的客户端的连接。
- 不会修复弃用版本中的错误。
以下表格提供了弃用计划。
已宣布 | 生效 | 最低版本 | 理由 |
---|---|---|---|
目前没有针对 xmtp-ios 的弃用计划。 |
欢迎按照以下这些 贡献指南 提交错误报告、功能请求和PR。
production
和 dev
网络环境
XMTP XMTP提供 production
和 dev
网络环境来支持您项目的开发阶段。
生产和开发网络是完全分开的,不可互换。例如,对于特定的区块链账户,其在开发网络的XMTP身份与在生产网络的XMTP身份完全是不同的,与其相关的消息也是如此。此外,在开发网络上创建的XMTP身份和消息无法从或移动到生产网络,反之亦然。
env
参数接受三个有效值之一:dev
、production
或local
。以下是何时使用每个环境的最佳实践:
-
dev
:用于使客户端与开发网络通信。作为一种最佳实践,在开发和测试你的应用程序时,将env
设置为dev
。遵循此最佳实践可以将测试消息隔离到开发收件箱中。 -
production
:用于使客户端与生产网络通信。作为一种最佳实践,当你的应用程序为真实用户提供服务时,将env
设置为production
。遵循此最佳实践可以将真实用户之间的消息隔离到生产收件箱中。 -
local
:用于使客户端与本地运行的XMTP节点通信。例如,XMTP节点开发者可以将env
设置为local
来生成指向本地运行的节点的客户端流量进行测试。
生产网络配置为无限期存储消息。XMTP可能会偶尔从开发网络删除消息和密钥,并在XMTP Discord社区中提前通知。