关于 using-ably
Using Ably 是面向开发者的技能,适用于构建或修改使用 Ably 实时消息、协作、聊天或 AI 流式传输的应用。它强调核对最新官方文档、选择合适的产品与 SDK,并规避常见集成错误。
主要功能
- 01指导选择 Ably 产品与 SDK
- 02核对最新文档的工作流程
- 03Realtime、REST 和产品 SDK 用法
- 04客户端与服务端认证建议
- 05频道设计建议
- 06集成错误检查
适用场景
- 01为应用功能选择 Ably SDK
- 02依据最新 API 文档生成代码
- 03检查认证和频道设计
- 04排查实时消息或 AI 流式集成
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ably/ agent-skills --skill using-ably安装后在 Claude Code 中输入/using-ably确认已加载。
详细介绍
使用 Ably
在构建、修改或生成使用 Ably 进行实时消息传递、聊天、协作或 AI 流式传输的应用程序代码时,请使用此技能。
Ably 是什么
Ably 是实时基础设施——正如你不会自己构建数据库或 CDN,你也不应该自己构建实时消息系统。Ably 负责处理复杂部分:在不可靠网络上的连接管理、可靠的消息排序与传递、通过状态恢复实现自动重连,以及弹性扩展至数十亿台设备。它支持多种平台(25+ 个 SDK)和协议(WebSocket、MQTT、SSE、HTTP),因此你可以专注于应用程序逻辑,而不必操心传输可靠性。
文档
请始终查阅 ably.com/docs 中的官方文档,了解最新的 API 参考。如需针对大模型优化的文档,请使用 ably.com/llms.txt。以下指南涵盖架构决策和常见错误,这些问题仅靠文档无法避免。
编写代码前
生成 Ably 代码前,请始终对照最新文档进行核实。 训练数据会过时——方法可能更名、参数可能更改、新产品也会推出。以下三项检查有助于避免大多数大模型代码生成错误。
1. 先获取最新文档
为任何 Ably 功能生成代码前:
- 获取
https://ably.com/llms.txt?source=using-ably,以取得文档 URL 索引 - 获取相关产品和平台的文档页面
- 根据获取的文档核实方法名称、参数、返回类型和软件包版本
- 如果文档中没有某项功能或方法,它很可能并不存在——不要凭空捏造
切勿猜测或拼接文档 URL。只能使用 llms.txt 或网页搜索结果中的 URL。如果某个 URL 返回 404,请返回 llms.txt 查找。也可以将网页搜索作为补充方式。如果无法访问 llms.txt,请改用网页搜索,或直接浏览 https://ably.com/docs?source=using-ably。
2. 确认 SDK 是否可用
并非所有产品都支持所有平台。生成代码前,请根据获取的文档确认该产品是否为用户的平台提供 SDK。
如果没有可用的 SDK:
- 明确说明:“[Product] 目前没有适用于 [platform] 的 SDK”
- 不要捏造 API
- 提供替代方案:直接使用 REST API、改用确实支持该平台的其他产品,或为受限设备使用 MQTT/SSE 协议适配器
3. 生成后检查
生成代码后,请检查以下常见错误模式:
- 客户端代码中包含 API 密钥? 必须使用
authUrl/authCallback,并采用 JWT 或令牌认证(参见第 3 节) - 聊天 SDK:是否在订阅前调用了
attach()? 应先订阅,以免消息悄无声息地丢失(参见第 7 节) - React:是否在组件内部创建 Ably 客户端却未清理? 应在外部创建一次并通过 provider 传递,或者使用 useEffect 并妥善清理(参见第 8 节)
- 服务端:在 REST 足以胜任时是否使用了 Realtime SDK? 发布消息、生成令牌和获取历史记录时,默认使用
Ably.Rest(参见第 2 节) - 卸载/退出时是否清理连接? 调用
realtime.close()(参见第 5 节) - 是否重新实现了内置行为? 不要为输入指示器添加自定义防抖、为光标添加自定义节流,或为 LiveObjects 添加自定义冲突解决机制(参见第 9 节)
- AI Transport:是否启用了通道规则? 命名空间需要
mutableMessages,否则流式传输会因错误93002而失败(参见第 2 节)
1. 了解产品和 SDK 全景
Ably 是一个多产品平台,而不是单一 SDK。选错产品或 SDK 是最常见的集成错误。
产品层:你要构建什么?
| 产品 | 用例 | SDK |
|---|---|---|
| Pub/Sub | 核心消息传递:实时仪表盘、通知、IoT、事件流 | ably(Realtime 或 REST) |
| AI Transport | 流式传输大模型 token、多 Agent 协调、可恢复的 AI 会话 | @ably/ai-transport |
| LiveObjects | 共享可变状态:计数器、键值映射、协作数据 | ably + LiveObjects 插件 |
| LiveSync | 数据库到前端同步(PostgreSQL 变更流) | @ably-labs/models + ADBC 连接器 |
| Chat | 聊天室、输入状态指示器、表情回应、消息历史记录、内容审核 | @ably/chat |
| Spaces | 协作光标、头像堆叠、成员位置、组件锁定 | @ably/spaces |
SDK 架构:两层
第 1 层 — 核心 SDK(直接用于 Pub/Sub、LiveObjects、LiveSync):
| SDK | 适用场景 | 连接方式 |
|---|---|---|
Ably.Realtime | 客户端需要订阅消息、presence 或状态变更 | 持久 WebSocket |
Ably.Rest | 服务端仅发布、生成 token、查询历史记录 | 无状态 HTTP |
这些是基础。LiveObjects 和 LiveSync 构建于核心 Realtime SDK 之上:LiveObjects 是通过客户端选项传入的插件,LiveSync 则通过 Models SDK 实现。
第 2 层 — 产品 SDK(针对特定用例的更高层抽象):
| SDK | 用途 |
|---|---|
@ably/chat | 聊天室、输入状态指示器、表情回应、消息历史记录、内容审核 |
@ably/spaces | 光标、头像堆叠、成员位置、组件锁定 |
@ably/ai-transport | 用于流式传输 AI Agent 输出的会话、运行和步骤;支持分支与历史记录的对话树 |
这些产品 SDK 依赖核心 Realtime SDK。创建它们时需传入 Ably.Realtime 实例。底层 Realtime 知识(身份验证、频道、presence)仍然适用。
决策规则
Q: Is this server-side or client-side?
├── Server-side:
│ ├── Publishing, token generation, history → Use Ably.Rest (stateless HTTP)
│ ├── Streaming AI agent output → Use @ably/ai-transport agent session (over Realtime)
│ └── Need persistent connection (subscribing) → Use Ably.Realtime
└── Client-side → Use Ably.Realtime, then:
├── Building chat? → Use @ably/chat (pass Realtime instance)
├── Building collaboration? → Use @ably/spaces (pass Realtime instance)
├── Consuming AI agent output? → Use @ably/ai-transport client session
├── Need shared state? → Use LiveObjects plugin with Realtime
├── Syncing database? → Use @ably-labs/models with ADBC connector
└── Otherwise → Use Ably.Realtime directly (Pub/Sub)
服务端经验法则: 服务端操作默认使用 Ably.Rest。只有在确实需要持久连接时,才在服务端使用 Ably.Realtime,例如 AI Agent 向客户端流式传输 token,或后端服务订阅事件。
常见错误
// WRONG: Using REST SDK then polling for messages
const rest = new Ably.Rest({ key: '...' });
setInterval(async () => {
const history = await rest.channels.get('updates').history();
}, 1000);
// RIGHT: Using Realtime SDK to subscribe
const realtime = new Ably.Realtime({ key: '...' });
realtime.channels.get('updates').subscribe((msg) => {
console.log('Received:', msg.data);
});
// WRONG: Using raw Pub/Sub for chat when Chat SDK exists
const channel = realtime.channels.get('chat-room');
channel.subscribe((msg) => { /* manually handling typing, reactions, history... */ });
// RIGHT: Using Chat SDK which handles all chat patterns
import { ChatClient } from '@ably/chat';
const chat = new ChatClient(realtime);
const room = await chat.rooms.get('my-room');
room.messages.subscribe((msg) => console.log(msg.text));
await room.typing.keystroke(); // built-in typing indicators
2. 使用现代 SDK 模式(v2.x)
Ably 的 JavaScript SDK v2.x 使用 async/await。切勿生成回调式的 1.x 模式。
// WRONG: Old 1.x callback pattern (deprecated)
const channel = realtime.channels.get('updates');
channel.subscribe('event', function(message) {
console.log(message.data);
});
channel.publish('event', { text: 'hello' }, function(err) {
if (err) console.error(err);
});
// RIGHT: Modern 2.x async/await pattern
const channel = realtime.channels.get('updates');
channel.subscribe('event', (message) => {
console.log(message.data);
});
await channel.publish('event', { text: 'hello' });
导入和初始化——保持简单:
// RIGHT: Standard ESM import
import Ably from 'ably';
const realtime = new Ably.Realtime({ key: 'your-key' });
const rest = new Ably.Rest({ key: 'your-key' });
// RIGHT: Chat SDK import
import { ChatClient } from '@ably/chat';
// WRONG: Don't invent type-based initialization
const options: Ably.Types.ClientOptions = { ... }; // unnecessary
如果遇到 ESM/CommonJS 导入错误,请检查 tsconfig.json 中的 "moduleResolution": "bundler" 或 "node16",并确认导入路径为 'ably'(而非子路径)。不要尝试多种导入方式——请根据你的环境查阅 SDK README。
服务端:默认使用 REST SDK。 只有在需要持久连接时,才在服务端使用 Ably.Realtime。
// WRONG: Realtime SDK on server just to publish
const realtime = new Ably.Realtime({ key: '...' }); // opens WebSocket unnecessarily
await realtime.channels.get('events').publish('update', data);
// RIGHT: REST SDK for server-side publish
const rest = new Ably.Rest({ key: '...' }); // stateless HTTP
await rest.channels.get('events').publish('update', data);
// WRONG: Hand-rolling token streaming onto a raw channel
const channel = realtime.channels.get('conversations:123');
const { serials: [msgSerial] } = await channel.publish({ name: 'response', data: '' });
for await (const event of llmStream) {
channel.appendMessage({ serial: msgSerial, data: event.text }); // reinvents AI Transport
}
// RIGHT: Use AI Transport — it owns the run/step lifecycle, cancellation, and history
import { createAgentSession } from '@ably/ai-transport/vercel';
const session = createAgentSession({ client: realtime, channelName: 'conversations:123' });
await session.connect();
// Then open a run and pipe your model stream into a step. Verify the run/step API
// against ably.com/docs/ai-transport/api/javascript/core/agent-session?source=using-ably
AI Transport 需要频道规则。 与 AI Transport 配合使用的每个命名空间都需要启用 Message annotations, updates, deletes, and appends 规则(mutableMessages: true)——流式传输会将 token 追加到一条消息中;未启用此规则时,追加操作会被拒绝并返回错误 93002。这是 AI Transport 最常见的故障,而且只会在首次追加时出现,因此表现为助手消息为空或仅包含第一个 token。请在控制面板中启用此规则,或运行 ably apps rules create "conversations" --mutable-messages。请参阅频道规则。
3. 身份验证
API 密钥仅供服务端使用。切勿将其暴露给客户端。
API 密钥不会过期。如果密钥泄露,攻击者将可以无限期访问,直到你重新生成密钥。
推荐:JWT 身份验证
JWT 是客户端应用程序推荐的身份验证方法。
为什么选择 JWT 而不是 Ably 令牌:
- 无需与 Ably 服务往返通信——后端可直接创建并签署 JWT
- 可集成现有身份验证系统——使用你已有的标准 JWT 库
- 每个令牌单独定义功能权限——不受 API 密钥配置的限制
- 适用范围广——包括没有 Ably SDK 的环境(MQTT、嵌入式设备)
JWT 声明:
kid(标头):Ably API 密钥名称(例如,xVLyHw.abcdef)iat:签发时间戳(秒)exp:过期时间戳(秒)x-ably-capability:包含频道权限的 JSON 字符串x-ably-clientId(可选):用于 presence 的客户端身份
// Server-side: Create JWT for client
import jwt from 'jsonwebtoken';
app.post('/api/ably-auth', (req, res) => {
const apiKey = process.env.ABLY_API_KEY; // 'appId.keyId:keySecret'
const [keyName, keySecret] = apiKey.split(':');
const token = jwt.sign(
{
'x-ably-capability': JSON.stringify({
'room:*': ['subscribe', 'publish', 'presence'],
[`notifications:${req.user.id}`]: ['subscribe'],
}),
'x-ably-clientId': req.user.id,
},
keySecret,
{
expiresIn: '1h',
keyid: keyName,
}
);
res.json(token);
});
// Client-side: Use JWT with Ably
const realtime = new Ably.Realtime({
authUrl: '/api/ably-auth',
authMethod: 'POST',
});
备选方案:Ably Token Requests
对于大多数用例,推荐使用 JWT;但如果功能权限列表过长,超出 JWT 大小限制(JWT 必须能放入 HTTP 标头,约 8KB),或者你需要对客户端保密功能权限(因为客户端可以解码 JWT),则 Ably 令牌可能更合适。需要权衡的是,集成 Ably 令牌较为复杂,而且不支持 JWT 的所有功能,例如按频道限定的声明和按连接设置的速率限制。
// Ably's native token system (requires round-trip to Ably servers from backend)
app.post('/api/ably-token', (req, res) => {
const client = new Ably.Rest({ key: process.env.ABLY_API_KEY });
client.auth.createTokenRequest({
clientId: req.user.id,
capability: { 'room:*': ['subscribe', 'publish'] },
}).then(tokenRequest => res.json(tokenRequest));
});
规则
- 服务端:可以使用 API 密钥(
{ key: 'appId.keyId:keySecret' }) - 客户端:始终使用
authUrl或authCallback——切勿嵌入 API 密钥或直接传入静态token(令牌过期后不会自动刷新) - 如果需要 presence 功能,请设置
clientId——这是使用 presence 的必要条件 - 使用功能权限限制每个客户端可访问的频道和可执行的操作
4. 频道设计
频道将消息划分为不同主题。尽早确定命名方式——之后很难更改。
规则:
- 使用
:作为层级分隔符:chat:room-123、orders:user-456 - 频道名称区分大小写
- 不要为每条消息创建一个频道——频道是长期存在的主题
- 使用控制面板中的 规则 为一组频道应用设置(例如,持久化、推送通知)
常见模式:
chat:room-{roomId} # Chat rooms
notifications:user-{userId} # Per-user notifications
cursors:doc-{docId} # Collaborative editing
events:{eventType} # Event streaming
conversations:{sessionId} # AI Transport sessions
规则和功能权限会按原样匹配命名空间,直到第一个冒号,因此 conversation(单数)不会匹配 conversations:*。选定一种拼写,并在频道名称、频道规则和令牌功能权限中统一使用。
5. 连接管理
Realtime SDK 会自动管理重新连接。不要试图绕过它。
规则:
- 不要手动重新连接——SDK 会通过指数退避处理临时故障
- 监听连接状态变化以更新 UI:
realtime.connection.on('connected', () => { /* online */ });
realtime.connection.on('disconnected', () => { /* temporarily offline */ });
realtime.connection.on('suspended', () => { /* offline for extended period */ });
- 完成后调用
realtime.close()(组件卸载、页面关闭等) - 断开连接期间发布的消息会在重新连接时收到(在 2 分钟的恢复窗口内)
- 对于原始 Pub/Sub,使用频道
rewind,在重新连接的客户端附加到频道时向其提供最近的消息:
const channel = realtime.channels.get('updates:123', {
params: { rewind: '2m' } // Replay last 2 minutes on attach
});
- 对于 AI Transport,不要使用
rewind——客户端会话会先订阅,再读取历史记录,并通过view.loadOlder()向前翻阅历史,因此历史记录与实时流可以无缝衔接,既无遗漏也无重复
6. Presence
Presence 用于跟踪哪些客户端位于某个频道。可用于实现“谁在线”之类的功能。
规则:
- 身份验证时设置
clientId——这是使用 presence 的必要条件 - 调用
channel.presence.enter()加入,调用channel.presence.update()更新数据,调用channel.presence.leave()离开 - 使用
channel.presence.get()获取当前成员,使用channel.presence.subscribe()订阅变更 - 如果客户端意外断开连接,Ably 会在约 15 秒后移除该客户端(而非立即移除)
- 不要将 presence 用于高频数据(光标位置、输入坐标)——改用频道。Presence 用于低频状态(在线/离线、用户状态)
- 对于 Chat:使用
room.presence,而不是原始频道 presence - 对于 Spaces:使用
space.enter()/useMembers()hook。Spaces 提供专用于光标位置的cursorsAPI
7. Chat SDK:关键生命周期
如果使用 @ably/chat,以下是最常见的错误:
务必先订阅,再调用 attach()。 未先订阅就附加会导致消息悄然丢失——这是最糟糕的一类 Bug。
const room = await chat.rooms.get('my-room');
// WRONG: Attach without subscribing first — messages silently lost
await room.attach();
room.messages.subscribe((msg) => console.log(msg.text)); // may miss messages during attach
// RIGHT: Subscribe first, then attach
room.messages.subscribe((msg) => console.log(msg.text));
await room.attach();
使用实际提供的 API。 Chat SDK 不支持:线程、已读回执、文件附件或 room.messages.broadcast()。请使用 room.messages.send() 发送消息。
不要混用 Chat 和 Pub/Sub 模式。 如果使用 @ably/chat,请使用 room.messages、room.typing、room.reactions,不要使用原始的 channel.subscribe() / channel.publish()。
内置行为,无需重新实现:
- SDK 会自动控制输入状态指示器的频率
- SDK 会处理聊天室反应的投递
- 消息排序和历史记录分页均为内置功能
8. React 集成
Ably 为每种产品提供了 React Hooks:
| 产品 | 包 | 关键 Hooks |
|---|---|---|
| Pub/Sub | ably/react | useChannel、usePresence、useConnectionStateListener |
| Chat | @ably/chat/react | useMessages、useTyping、usePresence、useRoomReactions |
| Spaces | @ably/spaces/react | useMembers、useCursors、useLocations、useLocks |
关键规则:不要在每次渲染时创建新的 Ably 客户端。 这会导致每次都创建一个新的 WebSocket 连接,造成内存泄漏。
// WRONG: Creates new connection every render — memory leak
function Chat() {
const ably = new Ably.Realtime({ authUrl: '/api/ably-auth' });
// ...
}
// RIGHT: Create client once, pass via provider
const ably = new Ably.Realtime({ authUrl: '/api/ably-auth' });
function App() {
return (
<AblyProvider client={ably}>
<ChannelProvider channelName="chat:room-1">
<Chat />
</ChannelProvider>
</AblyProvider>
);
}
// ALSO RIGHT: Create in useEffect with proper cleanup
function App() {
const [client, setClient] = useState(null);
useEffect(() => {
const ably = new Ably.Realtime({ authUrl: '/api/ably-auth' });
setClient(ably);
return () => ably.close();
}, []);
// ...
}
9. Spaces 和 LiveObjects:内置行为
使用 Spaces(@ably/spaces)时:
- 光标位置批处理为内置功能,不要自行实现节流
- 成员位置跟踪通过
space.locations内置提供 - 组件锁定通过
space.locks自动处理资源争用
使用 LiveObjects 时:
- 可用的数据结构只有 Maps 和 Counters(不支持自定义对象类型)
- 冲突解决为内置功能,不要自行实现
- 重新连接后会自动恢复状态
- 为简化操作,优先使用基于路径的 API,而非实例 API
10. LiveSync:从数据库到前端
LiveSync 将数据库连接到前端:数据库 → ADBC Connector → Ably Channels → Models SDK → 前端。
关键限制: PostgreSQL 需要逻辑复制(14+;更改 WAL 级别需要重启)。MongoDB 需要副本集(不支持独立实例)。配置入口规则后,Database Connector 会自动处理频道映射。
务必获取最新的 LiveSync 文档,地址为 ably.com/docs/livesync——数据库权限和 Connector 配置会随版本变化。
11. 生产环境检查清单
上线前,请确认:
- 客户端代码中不包含 API 密钥——通过
authUrl/authCallback使用 JWT 或令牌认证 - 限定能力范围——不要向客户端授予
{"*":["*"]};请将权限限制为特定频道和操作 - 清理连接——在卸载/页面退出时调用
realtime.close(),避免连接泄漏 - 错误处理——监听
connection.on('failed'),并妥善处理认证失败 - 分离频道——分离不再需要的频道(
channel.detach()) - 消息大小——默认情况下,消息大小限制为 64KB;如果达到此限制,请拆分负载或重新考虑消息设计
- 幂等发布——在需要严格一次投递时设置唯一消息 ID
-
echoMessages: false——如果发布者不需要接收自己发布的消息,请设置此项(可节省带宽和成本) - 速率限制——Ably 会强制执行速率限制(例如,每个频道每秒 50 条消息、每个连接 200 个频道)。如果达到限制,请检查发布频率和频道扇出情况。请阅读限制文档
错误处理
Ably 错误代码的范围可能较宽(例如,40000、50000)——务必查看错误消息文本,不要只看代码。每个错误代码都有对应的帮助页面:https://help.ably.io/error/{code}。
12. 快速参考
产品文档: Pub/Sub | Chat | Spaces | LiveObjects | LiveSync | AI Transport
平台 SDK: JavaScript、React、Python、Ruby、Java、Kotlin、Swift、.NET、Go、PHP、Flutter — ably.com/docs/sdks
Ably CLI: 使用 npm install -g @ably/cli 安装,以发布测试消息、订阅频道并验证设置。运行 ably --help 可查看可用命令。
如需 API 参考,请从 ably.com/docs 开始。