Loading...
Loading...
Compare original and translation side by side
https://ably.com/llms.txt?source=using-ablyhttps://ably.com/docs?source=using-ablyhttps://ably.com/llms.txt?source=using-ablyhttps://ably.com/docs?source=using-ablyauthUrlauthCallbackattach()Ably.Restrealtime.close()authUrlauthCallbackattach()Ably.Restrealtime.close()| Product | Use Case | SDK |
|---|---|---|
| Pub/Sub | Core messaging: live dashboards, notifications, IoT, event streaming | |
| AI Transport | Streaming LLM tokens, multi-agent coordination, resumable AI sessions | |
| LiveObjects | Shared mutable state: counters, key-value maps, collaborative data | |
| LiveSync | Database-to-frontend sync (PostgreSQL change streams) | |
| Chat | Chat rooms, typing indicators, reactions, message history, moderation | |
| Spaces | Collaborative cursors, avatar stacks, member locations, component locking | |
| 产品 | 适用场景 | SDK |
|---|---|---|
| Pub/Sub | 核心消息传递:实时仪表板、通知、物联网、事件流 | |
| AI Transport | LLM令牌流、多Agent协调、可恢复AI会话 | |
| LiveObjects | 共享可变状态:计数器、键值映射、协作数据 | |
| LiveSync | 数据库到前端的同步(PostgreSQL变更流) | |
| Chat | 聊天室、打字指示器、消息反应、消息历史、内容审核 | |
| Spaces | 协作光标、头像堆叠、成员位置、组件锁定 | |
| SDK | Use When | Connection |
|---|---|---|
| Client needs to subscribe to messages, presence, or state changes | Persistent WebSocket |
| Server-side publish only, token generation, history queries | Stateless HTTP |
| SDK | Purpose |
|---|---|
| Rooms, typing indicators, reactions, message history, moderation |
| Cursors, avatar stacks, member locations, component locking |
Ably.Realtime| SDK | 使用场景 | 连接方式 |
|---|---|---|
| 客户端需要订阅消息、在线状态或状态变更 | 持久化WebSocket |
| 服务端仅需发布、令牌生成、历史查询 | 无状态HTTP |
| SDK | 用途 |
|---|---|
| 聊天室、打字指示器、消息反应、消息历史、内容审核 |
| 协作光标、头像堆叠、成员位置、组件锁定 |
Ably.RealtimeQ: Is this server-side or client-side?
├── Server-side:
│ ├── Publishing, token generation, history → Use Ably.Rest (stateless HTTP)
│ └── Need persistent connection (AI token streaming, 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)
├── Need shared state? → Use LiveObjects plugin with Realtime
├── Syncing database? → Use @ably-labs/models with ADBC connector
└── Otherwise → Use Ably.Realtime directly (Pub/Sub, AI Transport)Ably.RestAbly.RealtimeQ: 是服务端还是客户端场景?
├── 服务端:
│ ├── 发布、令牌生成、历史查询 → 使用Ably.Rest(无状态HTTP)
│ └── 需要持久连接(AI令牌流、订阅事件) → 使用Ably.Realtime
└── 客户端 → 使用Ably.Realtime,然后:
├── 构建聊天功能? → 使用@ably/chat(传入Realtime实例)
├── 构建协作功能? → 使用@ably/spaces(传入Realtime实例)
├── 需要共享状态? → 结合Realtime使用LiveObjects插件
├── 同步数据库? → 使用@ably-labs_models结合ADBC连接器
└── 其他场景 → 直接使用Ably.Realtime(Pub/Sub、AI Transport)Ably.RestAbly.Realtime// 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// 错误:使用REST SDK然后轮询消息
const rest = new Ably.Rest({ key: '...' });
setInterval(async () => {
const history = await rest.channels.get('updates').history();
}, 1000);
// 正确:使用Realtime SDK订阅
const realtime = new Ably.Realtime({ key: '...' });
realtime.channels.get('updates').subscribe((msg) => {
console.log('收到消息:', msg.data);
});
// 错误:已有Chat SDK却使用原生Pub/Sub构建聊天功能
const channel = realtime.channels.get('chat-room');
channel.subscribe((msg) => { /* 手动处理打字、反应、历史... */ });
// 正确:使用Chat SDK处理所有聊天模式
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(); // 内置打字指示器// 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 = { ... }; // unnecessarytsconfig.json"moduleResolution": "bundler""node16"'ably'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);
// RIGHT: Realtime on server for AI streaming (message-per-response pattern)
// See ably.com/docs/ai-transport/token-streaming/message-per-response?source=using-ably
const channel = realtime.channels.get('conversation:123');
const { serials: [msgSerial] } = await channel.publish({ name: 'response', data: '' });
for await (const event of llmStream) {
if (event.type === 'token') {
channel.appendMessage({ serial: msgSerial, data: event.text });
}
}// 错误:旧版1.x回调模式(已废弃)
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);
});
// 正确:现代2.x async/await模式
const channel = realtime.channels.get('updates');
channel.subscribe('event', (message) => {
console.log(message.data);
});
await channel.publish('event', { text: 'hello' });// 正确:标准ESM导入
import Ably from 'ably';
const realtime = new Ably.Realtime({ key: 'your-key' });
const rest = new Ably.Rest({ key: 'your-key' });
// 正确:Chat SDK导入
import { ChatClient } from '@ably/chat';
// 错误:不要发明基于类型的初始化
const options: Ably.Types.ClientOptions = { ... }; // 不必要tsconfig.json"moduleResolution": "bundler""node16"'ably'Ably.Realtime// 错误:仅为发布消息就在服务端使用Realtime SDK
const realtime = new Ably.Realtime({ key: '...' }); // 不必要地开启WebSocket
await realtime.channels.get('events').publish('update', data);
// 正确:服务端发布使用REST SDK
const rest = new Ably.Rest({ key: '...' }); // 无状态HTTP
await rest.channels.get('events').publish('update', data);
// 正确:服务端使用Realtime SDK进行AI流传输(消息逐段响应模式)
// 参考 ably.com/docs/ai-transport/token-streaming/message-per-response?source=using-ably
const channel = realtime.channels.get('conversation:123');
const { serials: [msgSerial] } = await channel.publish({ name: 'response', data: '' });
for await (const event of llmStream) {
if (event.type === 'token') {
channel.appendMessage({ serial: msgSerial, data: event.text });
}
}kidxVLyHw.abcdefiatexpx-ably-capabilityx-ably-clientId// 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',
});kidxVLyHw.abcdefiatexpx-ably-capabilityx-ably-clientId// 服务端:为客户端创建JWT
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);
});
// 客户端:结合JWT使用Ably
const realtime = new Ably.Realtime({
authUrl: '/api/ably-auth',
authMethod: 'POST',
});// 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));
});// Ably原生令牌系统(需要后端与Ably服务器往返)
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));
});{ key: 'appId.keyId:keySecret' }authUrlauthCallbacktokenclientId{ key: 'appId.keyId:keySecret' }authUrlauthCallbacktokenclientId:chat:room-123orders:user-456chat:room-{roomId} # Chat rooms
notifications:user-{userId} # Per-user notifications
cursors:doc-{docId} # Collaborative editing
events:{eventType} # Event streaming
conversation:{sessionId} # AI Transport sessions:chat:room-123orders:user-456chat:room-{roomId} # 聊天室
notifications:user-{userId} # 按用户推送的通知
cursors:doc-{docId} # 协作编辑
events:{eventType} # 事件流
conversation:{sessionId} # AI Transport会话realtime.connection.on('connected', () => { /* online */ });
realtime.connection.on('disconnected', () => { /* temporarily offline */ });
realtime.connection.on('suspended', () => { /* offline for extended period */ });realtime.close()rewindconst channel = realtime.channels.get('conversation:123', {
params: { rewind: '2m' } // Replay last 2 minutes on attach
});realtime.connection.on('connected', () => { /* 在线 */ });
realtime.connection.on('disconnected', () => { /* 临时离线 */ });
realtime.connection.on('suspended', () => { /* 长时间离线 */ });realtime.close()rewindconst channel = realtime.channels.get('conversation:123', {
params: { rewind: '2m' } // 连接时重播最近2分钟的消息
});clientIdchannel.presence.enter()channel.presence.update()channel.presence.leave()channel.presence.get()channel.presence.subscribe()room.presencespace.enter()useMembers()cursorsclientIdchannel.presence.enter()channel.presence.update()channel.presence.leave()channel.presence.get()channel.presence.subscribe()room.presencespace.enter()useMembers()cursors@ably/chatattach()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();room.messages.broadcast()room.messages.send()@ably/chatroom.messagesroom.typingroom.reactionschannel.subscribe()channel.publish()@ably/chatattach()const room = await chat.rooms.get('my-room');
// 错误:先attach再订阅——消息会静默丢失
await room.attach();
room.messages.subscribe((msg) => console.log(msg.text)); // 可能会丢失attach过程中的消息
// 正确:先订阅再attach
room.messages.subscribe((msg) => console.log(msg.text));
await room.attach();room.messages.broadcast()room.messages.send()@ably/chatroom.messagesroom.typingroom.reactionschannel.subscribe()channel.publish()| Product | Package | Key Hooks |
|---|---|---|
| Pub/Sub | | |
| Chat | | |
| Spaces | | |
// 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();
}, []);
// ...
}| 产品 | 包 | 核心钩子 |
|---|---|---|
| Pub/Sub | | |
| Chat | | |
| Spaces | | |
// 错误:每次渲染创建新连接——内存泄漏
function Chat() {
const ably = new Ably.Realtime({ authUrl: '/api/ably-auth' });
// ...
}
// 正确:创建一次客户端,通过provider传递
const ably = new Ably.Realtime({ authUrl: '/api/ably-auth' });
function App() {
return (
<AblyProvider client={ably}>
<ChannelProvider channelName="chat:room-1">
<Chat />
</ChannelProvider>
</AblyProvider>
);
}
// 同样正确:在useEffect中创建并正确清理
function App() {
const [client, setClient] = useState(null);
useEffect(() => {
const ably = new Ably.Realtime({ authUrl: '/api/ably-auth' });
setClient(ably);
return () => ably.close();
}, []);
// ...
}@ably/spacesspace.locationsspace.locks@ably/spacesspace.locationsspace.locksauthUrlauthCallback{"*":["*"]}realtime.close()connection.on('failed')channel.detach()echoMessages: falseauthUrlauthCallback{"*":["*"]}realtime.close()connection.on('failed')channel.detach()echoMessages: falsehttps://help.ably.io/error/{code}https://help.ably.io/error/{code}npm install -g @ably/cliably --helpnpm install -g @ably/cliably --help