mcpField
U
using-ably

using-ably

github.com/ably开发1242 次安装13 周前更新
代码仓库

关于 using-ably

Using Ably 是面向开发者的技能,适用于构建或修改使用 Ably 实时消息、协作、聊天或 AI 流式传输的应用。它强调核对最新官方文档、选择合适的产品与 SDK,并规避常见集成错误。

主要功能

  1. 01指导选择 Ably 产品与 SDK
  2. 02核对最新文档的工作流程
  3. 03Realtime、REST 和产品 SDK 用法
  4. 04客户端与服务端认证建议
  5. 05频道设计建议
  6. 06集成错误检查

适用场景

  1. 01为应用功能选择 Ably SDK
  2. 02依据最新 API 文档生成代码
  3. 03检查认证和频道设计
  4. 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 功能生成代码前:

  1. 获取 https://ably.com/llms.txt?source=using-ably,以取得文档 URL 索引
  2. 获取相关产品和平台的文档页面
  3. 根据获取的文档核实方法名称、参数、返回类型和软件包版本
  4. 如果文档中没有某项功能或方法,它很可能并不存在——不要凭空捏造

切勿猜测或拼接文档 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 提供专用于光标位置的 cursors API

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/Subably/reactuseChannel、usePresence、useConnectionStateListener
Chat@ably/chat/reactuseMessages、useTyping、usePresence、useRoomReactions
Spaces@ably/spaces/reactuseMembers、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 开始。

评论

支持 Markdown