聊天室
聊天室适合直播间、语聊房、活动互动区:没有固定成员,用户打开页面时进入、离开页面时离开,一个聊天室可以有几十万人同时在线。消息只推给此刻在聊天室里的用户,不进入会话列表,不计未读数,也没有离线推送;刚进入的用户可以看到最近的几十条消息。
典型的用法是:你的服务端在直播开始时创建聊天室,把聊天室 ID 交给页面;页面调用 im.chatrooms.enter() 进入,得到一个 ChatroomHandle,用它读取聊天室信息、显示消息、发送消息;离开页面时调用 leave()。
const room = await im.chatrooms.enter('100310941534519296');
room.subscribe(() => {
const { info, messages } = room.getSnapshot();
render(info.name, info.member_count, messages);
});
await room.send({ type: 'text', body: { text: '主播晚上好' } });聊天室的完整规则(人数上限、身份、消息的优先级与限流、最近的消息、属性)见服务端文档聊天室管理,本页只说明在网页中怎么用。
查找聊天室
im.chatrooms.list() 列出应用中对客户端公开的聊天室(服务端创建时 listed 为 true、状态正常的),可以按名称前缀搜索;不公开的聊天室只能按 ID 进入。im.chatrooms.get() 查询一个聊天室的信息,不需要先进入。
const page = await im.chatrooms.list({ name_prefix: '周末', limit: 20 });
for (const item of page.items) {
console.log(item.room_id, item.name, item.member_count);
}
// 还有下一页时:im.chatrooms.list({ name_prefix: '周末', cursor: page.next_cursor })在列表页上同时显示多个聊天室的在线人数,用 onlineCounts() 一次查询,最多 100 个:
const counts = await im.chatrooms.onlineCounts(['100310941534519296', '100311027031212032']);
// [{ room_id: '100310941534519296', member_count: 12034 }, ...],不存在和已解散的不在结果中在线人数按用户计算(同一用户多台设备只算一次),最多缓存 5 秒,是近似值,适合“1.2 万人在线”这样的展示。
进入聊天室
enter() 在进入成功后兑现,返回这个聊天室的句柄。进入时可以带一段最长 512 字节的 ext(如“VIP3”),随进入的通知发给其他成员。
import { DRError } from '@deeprespond/im-web';
try {
const room = await im.chatrooms.enter('100310941534519296', { ext: 'VIP3' });
console.log(room.getSnapshot().state); // 'in'
} catch (err) {
if (err instanceof DRError && err.reason === 'chatroom_banned') {
showToast('你已被禁止进入该直播间');
} else if (err instanceof DRError && err.code === 'not_found') {
showToast('直播已结束');
} else {
throw err;
}
}进入的条件:聊天室存在且正常;你没有被这个聊天室封禁;聊天室没有满员(所有者和管理员不受人数上限的限制)。
- 同一个页面多次进入:同一个标签页对同一个聊天室多次调用
enter(),得到的是同一个句柄,SDK 记下进入的次数,leave()调用同样的次数后才真正离开。页面中的几个组件可以各自进入和离开同一个聊天室,互不影响。 - 进入的数量:同一浏览器(全部标签页合计)同时进入的聊天室数有上限(默认 10 个),超出时
enter()以limit_exceeded(connection_room_limit)拒绝。 - 进入太频繁:同一时刻大量用户进入同一个聊天室时,服务端会让一部分人稍后再进入,SDK 自动等待并重试,
enter()可能需要几秒才兑现。 - 还没连上长连接:进入必须经过长连接,长连接没有就绪时
enter()等待,最长 15 秒,超时以not_connected拒绝。
切换直播间
在直播间之间切换时,进入新的聊天室时带上 leave_others: true,会同时离开本标签页进入的其他聊天室(其他标签页进入的不受影响),旧的句柄的 state 变为 left:
const next = await im.chatrooms.enter('100311027031212032', { leave_others: true });离开聊天室
const room = await im.chatrooms.enter('100310941534519296');
// ……
await room.leave();leave()立即离开,其他成员会收到离开的通知(人数不多的聊天室)。如果你没有调用leave(),关闭页面、刷新页面时 SDK 会自动离开;网络断开 15 秒后服务端也会判定你已离开。- 单页应用切换路由、组件卸载时,请调用
leave()。 - 离开之后句柄就结束了:快照的
state为left,不再变化,再调用send()等方法以invalid_state拒绝。要再次进入,重新调用enter(),会得到新的句柄。 im.chatrooms.entered是本标签页当前进入的聊天室(聊天室 ID 到句柄)。
多个标签页
同一浏览器的多个标签页共用一条长连接,每个标签页各自进入、离开自己的聊天室:
- 一个标签页进入的聊天室,只有这个标签页收到它的消息和通知;
- 多个标签页进入同一个聊天室时,服务端看到的是同一个用户、同一条连接,最后一个标签页离开时才真正离开;
- 关闭一个标签页,只离开它进入的聊天室;
leave_others只离开调用它的标签页进入的聊天室。
断线后自动重新进入
网络断开又恢复、SDK 重新建立长连接后,会自动重新进入断开前所在的聊天室,句柄不变,你不需要做任何处理。重新进入期间快照的 state 为 reentering,可以显示“正在重新连接”;成功后回到 in,并重新获取聊天室信息、属性和最近的消息。
断开期间如果你被移出、被封禁,或聊天室已解散、已被封禁,重新进入不会成功,句柄按被移出处理。
断开期间的消息不会补发:聊天室消息只推给此刻在线的连接。
被移出聊天室
你被所有者、管理员或你的服务端移出、封禁,或者聊天室被解散、被封禁时,句柄触发 removed 事件,快照的 state 变为 removed,removed 中是原因。SDK 不会自动重新进入。
const room = await im.chatrooms.enter('100310941534519296');
room.on('removed', ({ reason, expires_at }) => {
const text: Record<string, string> = {
kicked: '你已被移出直播间',
banned: expires_at ? `你已被禁止进入,至 ${new Date(expires_at).toLocaleString()}` : '你已被永久禁止进入',
dismissed: '直播已结束',
disabled: '该直播间已被关闭',
};
showToast(text[reason] ?? '你已离开直播间');
});reason | 说明 |
|---|---|
kicked | 被移出,operator 为操作人的用户名(你的服务端操作时为 null)。可以再次进入 |
banned | 被封禁,expires_at 为封禁的到期时间,永久封禁为 null。到期前不能再进入 |
dismissed | 聊天室已解散 |
disabled | 聊天室已被封禁 |
聊天室信息与在线人数
快照的 info 是聊天室的信息:名称、简介、封面、公告、所有者、管理员、在线人数、是否全员禁言等,见 ChatroomInfo。资料变化时(包括全员禁言、所有者和管理员的变化)SDK 自动更新快照,订阅 subscribe() 即可:
const room = await im.chatrooms.enter('100310941534519296');
const off = room.subscribe(() => {
const { info } = room.getSnapshot();
render({
title: info.name,
cover: info.avatar_url,
online: info.member_count,
muteAll: info.mute_all,
});
});
// 不再需要时 off()info.self 是你在这个聊天室中的身份和状态:
| 字段 | 说明 |
|---|---|
role | owner 所有者 / admin 管理员 / member 普通用户 |
muted、muted_until | 你是否被禁言、禁言的到期时间(永久禁言为 null)。禁言到期时 SDK 自动恢复为 false |
allowlisted | 你是否在白名单中:全员禁言时白名单中的用户仍然可以发言 |
在线人数 member_count 在进入时取得;人数不多的聊天室(不超过运行策略 chatroom_member_notify_limit,默认 100 人)在有人进出时随通知更新。人数更多的聊天室不推送进出通知,需要定时刷新在线人数时,调用 refresh() 或 im.chatrooms.get():
const room = await im.chatrooms.enter('100310941534519296');
const timer = setInterval(() => void room.refresh().catch(() => {}), 30_000);接收消息
快照的 messages 是进入后获取的最近的消息,加上之后收到和发出的消息,按到达的顺序排列,最多保留 500 条(更早的自动丢弃)。通常直接按 messages 渲染消息列表:
const room = await im.chatrooms.enter('100310941534519296');
room.subscribe(() => {
for (const m of room.getSnapshot().messages) {
if (m.type === 'text') {
render(m.sender_nickname ?? m.sender, (m.body as { text: string }).text, m._local.status);
}
}
});需要对每条新消息做处理(如播放礼物特效)时,监听 message 事件:
const room = await im.chatrooms.enter('100310941534519296');
room.on('message', ({ message }) => {
if (message.type === 'custom' && message.body?.gift) {
render('gift', message.sender_nickname, message.body.gift);
}
});- 管理员和官方消息:
sender_role为发送时的身份(owner、admin、member),可以据此显示“房管”标识;_local.trusted为true的消息由你的服务端、控制台或系统发出,不是用户在客户端发送的,可以显示为官方消息。不要根据ext中的内容判断,ext由发送者自己填写。 - 系统消息:
sender_type为system的消息,sender、sender_nickname、sender_avatar_url、sender_role都为null。 - 消息过多时:热闹的聊天室中,服务端每秒推给每个连接的消息有上限,超出时先丢弃低优先级的,再丢弃普通的(高优先级的正常情况下不会丢弃)。有消息被丢弃时句柄触发
dropped事件,可以提示“消息过多,已省略部分”:
room.on('dropped', ({ count }) => {
render('hint', `消息过多,已省略 ${count} 条`);
});- 撤回:消息被撤回时,SDK 把它从
messages中去掉,并触发recalled事件; - 不保证送达和顺序:网络异常时消息可能丢失,不同用户发出的消息到达的先后可能略有不同。需要可靠送达的内容(如礼物结算)请以高优先级发送,并由你的业务另行保证。
最近的消息
进入聊天室后,SDK 自动获取最近的消息并放在 messages 的前面,你不需要另外调用。最近的消息只有普通和高优先级的,最多 chatroom_history_size 条(运行策略,默认 50 条),只保留 24 小时;运行策略设为 0 时不保存,进入后 messages 从空开始。最近的消息不能向前翻页。
收到服务端的提示、可能漏收了内容时,SDK 会自动重新获取聊天室信息、属性和最近的消息,并与已有的消息合并、去重。你也可以调用 refresh() 手动刷新。
发送消息
send() 发送一条消息,支持文本、图片、语音、视频、文件、位置和自定义消息,内容的格式与单聊、群聊消息相同,见消息格式。
const room = await im.chatrooms.enter('100310941534519296');
const sent = await room.send({ type: 'text', body: { text: '唱得太好了!' } });
console.log(sent.message_id, sent._local.status); // 'sent'发送时,普通和高优先级的消息先以 sending 状态出现在 messages 中,成功后变为 sent,失败时变为 failed 并以错误拒绝。网络错误、超时时 SDK 自动重试,1 分钟内结束;被限流(rate_limited)时不自动重试,请提示用户“发送太频繁”。
优先级
每条聊天室消息有一个优先级,决定消息过多时是否会被限流或丢弃:
priority | 用途 | 谁可以发送 | 发送时超出聊天室的额度 |
|---|---|---|---|
high | 系统通知、管理员公告、礼物结算等不能丢的消息 | 所有者、管理员 | 以 rate_limited 拒绝 |
normal | 普通聊天,默认 | 所有人 | 以 rate_limited 拒绝 |
low | 点赞、弹幕特效、进场特效等丢了无妨的高频消息 | 所有人 | 静默丢弃,返回的消息 _local.status 为 dropped,不必提示用户 |
const room = await im.chatrooms.enter('100310941534519296');
// 进场特效:低优先级
await room.send({ type: 'custom', body: { effect: 'enter', level: 3 } }, { priority: 'low' });
// 管理员公告:高优先级,普通用户发送以 permission_denied(priority_denied)拒绝
await room.send({ type: 'text', body: { text: '请文明发言' } }, { priority: 'high' });低优先级的消息不先出现在 messages 中,也不自动重试;服务端推回来后才出现在 messages 中,被丢弃的不会出现。
每个聊天室每秒接受的普通和低优先级消息数由运行策略 chatroom_msg_per_second 决定(默认 40 条),见消息的优先级与下发。
大小上限与图片
一条聊天室消息(类型、内容和 ext 合计)不能超过运行策略 max_chatroom_message_bytes(默认 2 KB),超出时 SDK 在本地以 local_validation(too_large)拒绝。
发送图片等附件时,先上传文件,再把地址写进消息(聊天室不支持直接传入文件):
declare const file: File; // 用户选择的图片
const room = await im.chatrooms.enter('100310941534519296');
const info = await im.files.upload(file, { purpose: 'attachment', kind: 'image' });
await room.send({
type: 'image',
body: { url: info.url, width: info.width ?? undefined, height: info.height ?? undefined, size: info.size },
});上传和显示附件见文件。
禁言
你被禁言(info.self.muted 为 true),或聊天室开启了全员禁言(info.mute_all 为 true)而你不是所有者、管理员、也不在白名单中时,不能发送消息和设置属性,发送以 chatroom_muted 拒绝,details.reason 为 member(你被禁言)或 all(全员禁言)。请据快照禁用输入框:
const room = await im.chatrooms.enter('100310941534519296');
room.subscribe(() => {
const { info } = room.getSnapshot();
const self = info.self;
const privileged = self?.role === 'owner' || self?.role === 'admin' || self?.allowlisted === true;
const canSpeak = !self?.muted && (!info.mute_all || privileged);
render('input', { disabled: !canSpeak, placeholder: canSpeak ? '说点什么' : '你已被禁言' });
});禁言、解除禁言、全员禁言的变化会实时更新快照;禁言到期时 SDK 在本地自动恢复。用户被全局禁言(聊天室场景)时,发送以 user_muted 拒绝,见联系人与用户。
点赞
点赞这类高频操作不要每点一次发一条消息。like() 把一秒内的多次点赞合并成一条低优先级的消息发送,不等待结果:
// 用户每点一次
room.like();
// 其他人的点赞(同一个人一秒内的合并为一次)
room.on('like', ({ username, count }) => {
render('hearts', username, count);
});点赞不会出现在 messages 中,只以 like 事件交给你。点赞是低优先级的消息,聊天室消息过多时可能被丢弃。
撤回消息
const room = await im.chatrooms.enter('100310941534519296');
const sent = await room.send({ type: 'text', body: { text: '发错了' } });
if (sent.message_id) {
await room.recall(sent.message_id);
}- 普通用户可以在运行策略
recall_window_seconds(默认 120 秒)内撤回自己的消息; - 管理员可以撤回发送者现在是普通用户的消息,所有者可以撤回任何人的消息;
- 只能撤回还在最近的消息中的消息,已经不在的以
not_found拒绝。
撤回后,所有人的 messages 中这条消息被去掉,并触发 recalled 事件:
room.on('recalled', ({ message_id, recalled_by }) => {
render('hint', recalled_by ? `${recalled_by} 撤回了一条消息` : '一条消息已被撤回');
});成员列表
members() 分页返回此刻在聊天室里的用户,按进入时间从早到晚排列,每页最多 100 个。翻页时 SDK 已按用户名去重。
const room = await im.chatrooms.enter('100310941534519296');
let page = await room.members({ limit: 50 });
render(page.items.map((m) => `${m.nickname || m.username}(${m.role})`));
if (page.next_cursor) {
page = await room.members({ cursor: page.next_cursor, limit: 50 });
}人数不多的聊天室(不超过运行策略 chatroom_member_notify_limit,默认 100 人),有人进入和离开时句柄触发 memberJoined、memberLeft 事件,同时更新快照中的在线人数。人数更多的聊天室不推送这两个事件;需要显示“某某进入直播间”时,请让进入的用户自己发一条低优先级的自定义消息。
room.on('memberJoined', (p) => {
const { username, nickname, ext } = p as { username: string; nickname: string; ext: string | null };
render('hint', `${nickname || username} 进入了直播间`, ext);
});
room.on('memberLeft', (p) => {
const { username } = p as { username: string; reason: string };
render('hint', `${username} 离开了直播间`);
});| 事件 | 载荷的字段 |
|---|---|
memberJoined | room_id、username、nickname、avatar_url、role、ext(进入时带的 ext)、joined_at、member_count |
memberLeft | room_id、username、reason(left 主动离开 / disconnected 断开 / kicked 被移出 / banned 被封禁)、member_count |
聊天室属性
属性是聊天室的自定义键值对,全体成员可见,常用于语聊房的麦位(如 seat_1 为 zhangsan)、直播间的玩法状态。快照的 attributes 是当前的全部属性,变化时 SDK 自动更新快照。
const room = await im.chatrooms.enter('100310941534519296');
room.subscribe(() => {
const { attributes } = room.getSnapshot();
render('seat1', attributes.seat_1?.value ?? '空位');
});设置和删除属性
const room = await im.chatrooms.enter('100310941534519296');
// 上麦:离开聊天室(包括断线 15 秒后)时自动删除,麦位不会一直被占着
const result = await room.setAttributes({ seat_1: 'zhangsan' }, { auto_delete: true });
if (result.failed.seat_1) {
showToast('这个麦位已经有人了');
}
// 下麦
await room.removeAttributes(['seat_1']);- 谁能修改:在聊天室里的用户可以设置和删除没人设置过的键和自己设置的键;别人设置的键在
failed中返回permission_denied(attribute_owned),其他键照常写入。所有者和管理员带上force: true可以覆盖和删除别人(包括你的服务端)设置的键,普通用户带force以permission_denied(role_required)拒绝。 - 禁言:被禁言的用户,以及全员禁言时不在白名单中的普通用户,不能设置属性(以
chatroom_muted拒绝),可以删除自己设置的属性。 - 格式:每个聊天室最多 100 个键。键为 1 到 128 个字符,只能包含字母、数字和
_、-、.、:;值为字符串,最长 4096 字节。一次最多设置或删除 20 个键,一次设置的键和值合计不超过 16 KB,超出时 SDK 在本地以local_validation拒绝。 - 频率:每个聊天室每秒最多 20 次属性写入。每秒变化多次的状态(如实时点赞数)不适合放在属性中,请用低优先级的自定义消息。
- 设置后,属性的值还会经过内容安全检查,违规的属性会被删除。
公告
公告是快照中 info.announcement,每次修改都会更新 announcement_updated_at。要提示“公告已更新”,记下用户看过的公告时间,与它比较:
const room = await im.chatrooms.enter('100310941534519296');
room.subscribe(() => {
const { info } = room.getSnapshot();
const seen = localStorage.getItem(`announcement:${info.room_id}`);
if (info.announcement && info.announcement_updated_at && info.announcement_updated_at !== seen) {
render('announcement', info.announcement);
localStorage.setItem(`announcement:${info.room_id}`, info.announcement_updated_at);
}
});所有者和管理员可以修改公告:
await room.manage.update({ announcement: '今晚 8 点开播,请文明发言' });管理聊天室
所有者和管理员可以在客户端管理聊天室,接口在句柄的 manage 下(需要先进入聊天室)。身份不满足时以 permission_denied(role_required,details.required_role 为需要的身份)拒绝。普通用户的界面中请隐藏这些入口,按 info.self.role 判断。
| 操作 | 方法 | 所有者 | 管理员 |
|---|---|---|---|
| 修改名称、简介、封面、公告 | manage.update() | 可以 | 可以 |
| 修改人数上限、是否出现在列表中 | manage.update() | 可以 | 不可以 |
| 开启、关闭全员禁言 | manage.setMuteAll() | 可以 | 可以 |
| 禁言、解除禁言,查看禁言名单 | manage.mute()、unmute()、mutes() | 可以 | 只能处置普通用户 |
| 封禁、解除封禁,查看封禁名单 | manage.ban()、unban()、bans() | 可以 | 只能处置普通用户 |
| 移出 | manage.kick() | 可以 | 只能移出普通用户 |
| 管理白名单 | manage.allowlist | 可以 | 可以 |
| 设置、取消管理员 | manage.setAdmin() | 可以 | 不可以 |
| 转让所有者 | manage.transferOwner() | 可以 | 不可以 |
| 解散聊天室 | manage.dismiss() | 可以 | 不可以 |
| 发送高优先级的消息 | send() | 可以 | 可以 |
| 撤回别人的消息 | recall() | 任何人的 | 普通用户的 |
- 没有人能禁言、移出或封禁所有者(
owner_protected);管理员不能处置其他管理员(admin_protected)。 - 解除禁言和封禁时,管理员只能解除管理员设置的,所有者和你的服务端设置的只有所有者能解除(
set_by_higher_role)。 - 聊天室被封禁期间,客户端的管理操作都以
chatroom_disabled拒绝。 - 普通用户可以进入、发送普通和低优先级的消息、查看成员和最近的消息、设置属性、撤回自己的消息。
禁言、封禁与移出
禁言、封禁、移出和白名单都是批量操作,一次最多 100 人,逐个处理,某个用户失败不影响其他用户,返回每个用户的结果:
const room = await im.chatrooms.enter('100310941534519296');
// 禁言 10 分钟;省略 duration_seconds 为永久禁言
const results = await room.manage.mute(['spammer01', 'spammer02'], { duration_seconds: 600, reason: '刷屏' });
for (const r of results) {
if ('code' in r) showToast(`${r.username}:${r.code}`); // 如 permission_denied(owner_protected)
}
await room.manage.unmute(['spammer02']);
// 封禁:在聊天室里的会被立即移出,到期前不能再进入
await room.manage.ban(['troll01'], { duration_seconds: 24 * 3600 });
// 移出:之后可以再次进入
await room.manage.kick(['troll02']);- 禁言:不能发送消息和设置属性,仍然可以留在聊天室里、接收消息。时长 1 秒到 10 年,或永久,到期自动解除。
- 封禁:在聊天室里的被移出,封禁期间不能进入。封禁管理员时,他同时不再是管理员。
- 移出:只是让他离开,他可以再次进入;要阻止再次进入请用封禁。
- 三个名单(禁言、封禁、白名单)与用户是否在聊天室里无关,可以对此刻不在聊天室里的用户操作。
查看名单(只列出未到期的):
const mutes = await room.manage.mutes({ limit: 20 });
for (const item of mutes.items) {
const entry = item as { username: string; nickname: string; muted_until: string | null; created_by_role: string };
render(entry.nickname || entry.username, entry.muted_until ?? '永久');
}名单项的字段见服务端文档的禁言名单项、封禁名单项和白名单项。
全员禁言与白名单
const room = await im.chatrooms.enter('100310941534519296');
await room.manage.setMuteAll(true);
// 嘉宾在全员禁言时仍然可以发言
await room.manage.allowlist.add(['guest01']);
await room.manage.allowlist.remove(['guest01']);
await room.manage.setMuteAll(false);全员禁言时,所有者、管理员和白名单中的用户仍然可以发言;被单独禁言的用户即使在白名单中也不能发言。白名单最多 500 人。
修改资料
manage.update() 只修改给出的字段,返回修改后的聊天室信息。可以带上读取到的 info.info_version(字段名为 version),防止覆盖别人同时做的修改,不一致时以 version_conflict 拒绝。
const room = await im.chatrooms.enter('100310941534519296');
const { info } = room.getSnapshot();
await room.manage.update({ name: '周末音乐会(返场)', description: '每周六晚八点', version: info.info_version });修改封面时,先以用途 chatroom_avatar 上传图片:
declare const file: File;
const room = await im.chatrooms.enter('100310941534519296');
const uploaded = await im.files.upload(file, { purpose: 'chatroom_avatar', kind: 'image' });
await room.manage.update({ avatar_url: uploaded.url });名称、简介和公告会经过内容安全检查,不通过的以 content_rejected 拒绝(details.field 为不通过的字段)。
管理员、所有者与解散
const room = await im.chatrooms.enter('100310941534519296');
await room.manage.setAdmin('lisi', true); // 设置管理员(最多 99 个)
await room.manage.setAdmin('lisi', false); // 取消管理员
await room.manage.transferOwner('wangwu'); // 转让后你成为普通用户所有者可以解散聊天室,解散后所有人(包括自己)被移出,removed 事件的原因为 dismissed,不能恢复:
await room.manage.dismiss();在客户端创建聊天室
默认只能由你的服务端或在控制台中创建聊天室。应用在运行策略中开启 client_chatroom_create_enabled 后,用户可以在客户端创建,创建者成为所有者。没有开启时 create() 以 permission_denied(client_create_disabled)拒绝,请按运行配置决定是否显示入口:
if (im.config?.client_chatroom_create_enabled) {
const info = await im.chatrooms.create({ name: '我的直播间', description: '欢迎来玩', listed: true });
const room = await im.chatrooms.enter(info.room_id);
}应用中的聊天室数有上限,达到时以 limit_exceeded(chatroom_limit)拒绝。
在 React 和 Vue 中使用
React 绑定提供 useChatroom:组件挂载时进入,卸载或聊天室 ID 变化时离开,返回句柄和随变化更新的快照。
import { useChatroom } from '@deeprespond/im-web-react';
function LiveRoom({ roomId }: { roomId: string }) {
const { handle, snapshot, error } = useChatroom(roomId);
if (error) return <p>进入失败</p>;
if (!handle || !snapshot) return <p>正在进入…</p>;
return (
<div>
<h1>{snapshot.info.name}({snapshot.info.member_count} 人在线)</h1>
<ul>
{snapshot.messages.map((m, i) => (
<li key={m.message_id ?? m.client_msg_id ?? i}>
{m.sender_nickname ?? m.sender}:{m.type === 'text' ? String((m.body as { text: string }).text) : '[消息]'}
</li>
))}
</ul>
<button onClick={() => void handle.send({ type: 'text', body: { text: '666' } })}>发送</button>
<button onClick={() => handle.like()}>点赞</button>
</div>
);
}Vue 绑定的 useChatroom 用法相同,参数可以是 ref 或 getter:
<script setup lang="ts">
import { useChatroom } from '@deeprespond/im-web-vue';
const props = defineProps<{ roomId: string }>();
const { handle, snapshot } = useChatroom(() => props.roomId);
function sendText(text: string) {
void handle.value?.send({ type: 'text', body: { text } });
}
</script>
<template>
<div v-if="snapshot">
<h1 v-text="snapshot.info.name" />
<ul>
<li v-for="(m, i) in snapshot.messages" :key="m.message_id ?? i" v-text="m.type === 'text' ? m.body?.text : '[消息]'" />
</ul>
<button @click="sendText('666')">发送</button>
</div>
</template>React 的 StrictMode 中组件会“挂载、卸载、再挂载”,由于同一标签页多次进入共用一个句柄、按次数离开,不会真的离开再进入。
接口参考
im.chatrooms.list()
分页列出对客户端公开(listed 为 true)、状态正常的聊天室,按创建时间从新到旧排列;按名称前缀搜索时按名称排列。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
p.name_prefix | string | 否 | 按名称前缀搜索,1 到 128 个字符 |
p.limit | number | 否 | 每页条数,默认 20,最多 100 |
p.cursor | string | 否 | 上一页返回的 next_cursor |
返回:Promise<Page<ChatroomListItem>>,items 为聊天室,next_cursor 为下一页的游标,没有下一页时为 null。
错误:invalid_argument(参数不合法)。
im.chatrooms.get()
查询一个聊天室的信息,含当前在线人数和你在其中的身份 self,不需要先进入。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
room_id | string | 是 | 聊天室 ID |
返回:Promise<ChatroomInfo>。
错误:not_found(聊天室不存在或已解散)。
im.chatrooms.onlineCounts()
批量查询在线人数。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
room_ids | string[] | 是 | 聊天室 ID,1 到 100 个 |
返回:Promise<Array<{ room_id: string; member_count: number }>>,按请求的顺序;不存在和已解散的聊天室不在结果中,已封禁的为 0。
错误:local_validation(room_ids 为空或超过 100 个)。
im.chatrooms.create()
在客户端创建聊天室,你成为所有者。需要应用开启 client_chatroom_create_enabled。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
p.name | string | 是 | 名称,1 到 128 个字符 |
p.description | string | 否 | 简介,最长 512 个字符 |
p.avatar_url | string | 否 | 封面地址,以用途 chatroom_avatar 上传后得到 |
p.announcement | string | 否 | 公告,最长 2048 个字符 |
p.max_members | number | 否 | 人数上限,1 到 500000;省略时跟随运行策略 |
p.listed | boolean | 否 | 是否出现在聊天室列表中,默认 true |
返回:Promise<ChatroomInfo>。创建后需要调用 enter() 才进入。
错误:permission_denied(client_create_disabled,应用没有开启客户端创建)、limit_exceeded(chatroom_limit)、content_rejected(名称、简介或公告没有通过内容安全检查)、invalid_argument(如封面地址不被允许 url_not_allowed)、app_unavailable(应用只读)、local_validation(缺少名称)。
im.chatrooms.enter()
进入聊天室,成功后兑现。同一标签页对同一个聊天室多次调用返回同一个句柄,进入次数加一。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
room_id | string | 是 | 聊天室 ID |
p.ext | string | 否 | 这次进入的附加信息,最长 512 字节,随进入的通知发给其他成员。只在第一次进入时生效 |
p.leave_others | boolean | 否 | 为 true 时同时离开本标签页进入的其他聊天室,用于切换直播间 |
返回:Promise<ChatroomHandle>。
错误:not_found(聊天室不存在或已解散)、chatroom_disabled(聊天室已被封禁)、permission_denied(chatroom_banned,你被封禁,details.expires_at 为到期时间,永久为 null)、limit_exceeded(chatroom_member_limit 聊天室已满;connection_room_limit 同时进入的聊天室已达上限)、rate_limited(进入太频繁,SDK 等待时间不超过 60 秒的会自动重试)、not_connected(长连接 15 秒内没有就绪)、local_validation(ext 超长)。
im.chatrooms.entered
只读属性,ReadonlyMap<string, ChatroomHandle>:本标签页当前进入的聊天室,聊天室 ID 到句柄。
handle.getSnapshot()
返回句柄当前的快照(只读,变化时整体替换),见 ChatroomSnapshot。
返回:ChatroomSnapshot。
handle.subscribe()
订阅快照的变化。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
listener | () => void | 是 | 快照变化时调用,在其中调用 getSnapshot() 取新的快照 |
返回:() => void,调用后取消订阅。
handle.on()
监听句柄的事件。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
event | string | 是 | 事件名,见下表 |
listener | (payload) => void | 是 | 处理函数 |
返回:() => void,调用后取消监听。
| 事件 | 载荷 | 说明 |
|---|---|---|
message | { message: ChatroomMessageView } | 收到新消息,或你发送的消息被服务端确认 |
recalled | { message_id: string; recalled_by: string | null; recalled_at: string } | 消息被撤回,recalled_by 为操作人,你的服务端撤回时为 null |
memberJoined | Record<string, unknown> | 有人进入(只在人数不多的聊天室推送),字段见成员列表 |
memberLeft | Record<string, unknown> | 有人离开(只在人数不多的聊天室推送),字段见成员列表 |
like | { username: string; count: number } | 有人点赞 |
dropped | { count: number } | 消息过多,服务端丢弃了 count 条 |
removed | { reason: string; operator: string | null; expires_at: string | null } | 你被移出,见被移出聊天室 |
handle.send()
发送一条聊天室消息。在进入完成之前调用的,等进入成功后再发送。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
content | OutgoingContent | 是 | 消息内容:{ type, body },格式见消息格式。附件要先上传,以地址发送 |
p.priority | 'high' | 'normal' | 'low' | 否 | 优先级,默认 normal。high 只有所有者和管理员能用 |
p.ext | Record<string, unknown> | 否 | 扩展字段 |
返回:Promise<ChatroomMessageView>,发送成功的消息;低优先级被丢弃时 _local.status 为 dropped、message_id 为 null。
错误:chatroom_muted(被禁言或全员禁言,details.reason 为 member 或 all,details.muted_until 为到期时间)、user_muted(被全局禁言)、permission_denied(priority_denied,普通用户发送高优先级的消息)、rate_limited(user_send_rate 你发送太频繁;room_send_rate 聊天室消息太多)、message_rejected(被内容安全或你的发送前回调拒绝)、payload_too_large、local_validation(内容不合法,如 too_large 超过 max_chatroom_message_bytes)、unsupported(chatroom_attachment,直接传入了文件)、not_chatroom_member、chatroom_disabled、not_found、app_unavailable、not_connected、invalid_state(句柄已结束)。
handle.like()
点赞。一秒内的多次点赞合并成一条低优先级的消息发送,不返回结果,也不抛出错误。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
count | number | 否 | 这次点赞的次数,正整数,默认 1 |
返回:无。
handle.setAttributes()
设置聊天室属性。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
attributes | Record<string, string> | 是 | 键到值,1 到 20 个,键和值合计不超过 16 KB |
p.auto_delete | boolean | 否 | 为 true 时这些键在你离开聊天室时自动删除,默认 false |
p.force | boolean | 否 | 覆盖别人设置的键,只有所有者和管理员能用,默认 false |
返回:Promise<AttributeResult>。部分键失败时不抛出错误,失败的键在 failed 中。
错误:chatroom_muted、permission_denied(role_required,普通用户带 force)、rate_limited(attribute_rate、room_attribute_rate)、invalid_argument(invalid_attribute_key、invalid_attribute_value)、local_validation(键数或大小超出)、not_chatroom_member、chatroom_disabled、app_unavailable、not_connected、invalid_state。
handle.removeAttributes()
删除聊天室属性。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
keys | string[] | 是 | 要删除的键,1 到 20 个;不存在的键忽略 |
p.force | boolean | 否 | 删除别人设置的键,只有所有者和管理员能用 |
返回:Promise<AttributeResult>。
错误:同 setAttributes(),被禁言时也可以删除。
handle.recall()
撤回一条聊天室消息。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
message_id | string | 是 | 消息 ID |
返回:Promise<{ changed: boolean }>,已经撤回过的 changed 为 false。
错误:permission_denied(recall_window_expired 超过可撤回的时间;recall_denied 无权撤回这条消息)、not_found(消息已不在最近的消息中)、invalid_state。
handle.members()
分页返回此刻在聊天室里的用户,按进入时间从早到晚排列,已按用户名去重。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page.limit | number | 否 | 每页条数,最多 100 |
page.cursor | string | 否 | 上一页返回的 next_cursor;不带时从第一页开始 |
返回:Promise<Page<ChatroomMember>>。
错误:not_chatroom_member、invalid_state。
handle.refresh()
重新获取聊天室信息(含在线人数)、属性和最近的消息,更新快照。
返回:Promise<void>。
错误:not_found、invalid_state。
handle.leave()
离开聊天室。进入次数减一,减到 0 时真正离开,之后句柄结束。进入还没完成时调用的,等进入完成后再处理。句柄已结束时调用没有作用。
返回:Promise<void>,不会失败。
handle.manage.update()
修改聊天室资料,只修改给出的字段。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
patch.name | string | 否 | 名称,不能清空(所有者、管理员) |
patch.description | string | 否 | 简介,空字符串表示清空(所有者、管理员) |
patch.avatar_url | string | 否 | 封面地址,空字符串表示清空(所有者、管理员) |
patch.announcement | string | 否 | 公告,空字符串表示清空(所有者、管理员) |
patch.max_members | number | null | 否 | 人数上限,null 表示跟随运行策略(所有者) |
patch.listed | boolean | 否 | 是否出现在聊天室列表中(所有者) |
patch.version | number | 否 | 读取到的 info.info_version,不一致时不修改 |
返回:Promise<ChatroomInfo>,修改后的信息。
错误:permission_denied(role_required)、version_conflict、content_rejected、invalid_argument、chatroom_disabled、app_unavailable、invalid_state。
handle.manage.setMuteAll()
开启或关闭全员禁言(所有者、管理员)。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
on | boolean | 是 | true 开启,false 关闭 |
返回:Promise<void>。
错误:permission_denied(role_required)、chatroom_disabled、invalid_state。
handle.manage.setAdmin()
设置或取消管理员(所有者)。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
username | string | 是 | 用户名,不要求他在聊天室里 |
admin | boolean | 是 | true 设置,false 取消 |
返回:Promise<void>。
错误:permission_denied(role_required;target_banned 对方在封禁中)、limit_exceeded(admin_limit,管理员已有 99 个)、not_found(用户不存在)、chatroom_disabled、invalid_state。
handle.manage.transferOwner()
把所有者转让给另一个用户(所有者),你成为普通用户。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
username | string | 是 | 新所有者的用户名,不要求他在聊天室里 |
返回:Promise<void>。
错误:permission_denied(role_required)、not_found、chatroom_disabled、invalid_state。
handle.manage.mute()
禁言用户(所有者、管理员)。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
usernames | string[] | 是 | 用户名,1 到 100 个 |
p.duration_seconds | number | 否 | 时长,1 到 315360000 秒(10 年);省略表示永久 |
p.reason | string | 否 | 原因 |
返回:Promise<ChatroomMemberResult[]>,每个用户的结果:成功为 muted 或 unchanged,失败带 code(如 permission_denied 的 owner_protected、admin_protected,not_found,limit_exceeded 的 mute_limit)。
错误:permission_denied(role_required)、chatroom_disabled、invalid_argument、local_validation(usernames 为空)、invalid_state。
handle.manage.unmute()
解除禁言(所有者、管理员)。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
usernames | string[] | 是 | 用户名,1 到 100 个 |
返回:Promise<ChatroomMemberResult[]>,成功为 unmuted 或 unchanged(原来没有禁言),失败如 permission_denied(set_by_higher_role)。
错误:同 mute()。
handle.manage.ban()
封禁用户(所有者、管理员),在聊天室里的同时被移出。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
usernames | string[] | 是 | 用户名,1 到 100 个 |
p.duration_seconds | number | 否 | 时长,1 到 315360000 秒;省略表示永久 |
p.reason | string | 否 | 原因 |
返回:Promise<ChatroomMemberResult[]>,成功为 banned 或 unchanged,失败如 owner_protected、admin_protected、ban_limit。
错误:同 mute()。
handle.manage.unban()
解除封禁(所有者、管理员)。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
usernames | string[] | 是 | 用户名,1 到 100 个 |
返回:Promise<ChatroomMemberResult[]>,成功为 unbanned 或 unchanged,失败如 set_by_higher_role。
错误:同 mute()。
handle.manage.kick()
把用户移出聊天室(所有者、管理员),他之后可以再次进入。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
usernames | string[] | 是 | 用户名,1 到 100 个 |
返回:Promise<ChatroomMemberResult[]>,成功为 removed 或 not_in_room(他不在聊天室里),失败如 owner_protected、admin_protected。
错误:同 mute()。
handle.manage.allowlist.list()
分页查询白名单(所有者、管理员)。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page.limit | number | 否 | 每页条数,默认 20,最多 100 |
page.cursor | string | 否 | 上一页返回的 next_cursor |
返回:Promise<Page<unknown>>,每项为 username、nickname、avatar_url、created_by、created_at。
错误:permission_denied(role_required)、invalid_state。
handle.manage.allowlist.add()
把用户加入白名单(所有者、管理员)。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
usernames | string[] | 是 | 用户名,1 到 100 个 |
返回:Promise<ChatroomMemberResult[]>,成功为 added 或 unchanged,失败如 limit_exceeded(allowlist_limit,白名单已有 500 人)。
错误:同 mute(),另有 app_unavailable(应用只读)。
handle.manage.allowlist.remove()
把用户移出白名单(所有者、管理员)。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
usernames | string[] | 是 | 用户名,1 到 100 个 |
返回:Promise<ChatroomMemberResult[]>,成功为 removed 或 unchanged。
错误:同 mute()。
handle.manage.mutes()
分页查询未到期的禁言(所有者、管理员)。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page.limit | number | 否 | 每页条数,默认 20,最多 100 |
page.cursor | string | 否 | 上一页返回的 next_cursor |
返回:Promise<Page<unknown>>,每项为 username、nickname、avatar_url、muted_until(永久为 null)、reason、created_by、created_by_role(owner、admin 或 server)、created_at。
错误:permission_denied(role_required)、invalid_state。
handle.manage.bans()
分页查询未到期的封禁(所有者、管理员)。参数同 mutes()。
返回:Promise<Page<unknown>>,每项的字段与禁言相同,只是 muted_until 换为 expires_at。
错误:permission_denied(role_required)、invalid_state。
handle.manage.dismiss()
解散聊天室(所有者),不能恢复。全部成员被移出,removed 的原因为 dismissed。
返回:Promise<void>。
错误:permission_denied(role_required)、chatroom_disabled(被平台封禁的聊天室)、invalid_state。
数据结构
ChatroomSnapshot
句柄的快照。
| 字段 | 类型 | 说明 |
|---|---|---|
info | ChatroomInfo | 聊天室信息,含 self |
attributes | Record<string, ChatroomAttribute> | 全部属性,键到属性 |
attributes_version | number | 属性的版本号,每次变化加一 |
messages | ChatroomMessageView[] | 最近的消息和之后收到、发出的消息,按到达顺序,最多 500 条 |
state | string | entering 正在进入 / in 在聊天室中 / reentering 断线后正在重新进入 / removed 已被移出 / left 已离开 |
removed | object | 只在 state 为 removed 时有:reason、operator、expires_at,见被移出聊天室 |
ChatroomInfo
| 字段 | 类型 | 说明 |
|---|---|---|
room_id | string | 聊天室 ID |
name | string | 名称 |
description | string | 简介 |
avatar_url | string | 封面地址,没有时为空字符串 |
announcement | string | 公告 |
announcement_updated_at | string | null | 公告的修改时间 |
announcement_updated_by | string | null | 在客户端修改公告的用户;你的服务端修改时为 null |
owner | string | null | 所有者的用户名,没有所有者时为 null |
admins | string[] | 管理员的用户名 |
max_members | number | 实际生效的人数上限 |
mute_all | boolean | 是否开启了全员禁言 |
listed | boolean | 是否出现在聊天室列表中 |
status | string | active 正常 / disabled 已封禁 / dismissed 已解散 |
disabled_by | string | null | 封禁方,未封禁时为 null |
member_count | number | 在线人数,近似值 |
info_version | number | 资料的版本号,每次变化加一 |
attributes_version | number | 属性的版本号 |
created_at | string | 创建时间 |
self | object | 你在这个聊天室中的状态:role(owner / admin / member)、in_room(是否在其中)、muted、muted_until(永久禁言为 null)、allowlisted |
时间都是 ISO 8601 格式的字符串,如 2026-10-04T19:19:52.149Z。
ChatroomListItem
im.chatrooms.list() 的一项。
| 字段 | 类型 | 说明 |
|---|---|---|
room_id | string | 聊天室 ID |
name | string | 名称 |
description | string | 简介 |
avatar_url | string | 封面地址 |
owner | string | null | 所有者 |
max_members | number | 人数上限 |
member_count | number | 在线人数 |
ChatroomMessageView
messages 中的一条消息:服务端的聊天室消息 ChatroomMessage,加上本地的状态 _local。
| 字段 | 类型 | 说明 |
|---|---|---|
room_id | string | 聊天室 ID |
message_id | string | null | 消息 ID;正在发送、发送失败或被丢弃的为 null |
client_msg_id | string | null | 发送者生成的去重 ID |
sender | string | null | 发送者的用户名,系统消息为 null |
sender_type | string | user 用户 / system 系统 |
sender_nickname | string | null | 发送时的昵称 |
sender_avatar_url | string | null | 发送时的头像 |
sender_role | string | null | 发送时的身份:owner / admin / member |
type | string | 消息类型:text、image、voice、video、file、location、custom |
body | Record<string, unknown> | null | 消息内容,见消息格式 |
ext | Record<string, unknown> | null | 扩展字段 |
priority | string | high / normal / low |
via | string | 发送途径:client 客户端 / openapi 你的服务端 / console 控制台 / system 系统 |
created_at | string | null | 发送时间;正在发送的为 null |
_local.status | string | sending 正在发送 / sent 已发送 / failed 发送失败 / dropped 低优先级的消息被丢弃 |
_local.error | DRError | 发送失败的原因,只在 failed 时有 |
_local.trusted | boolean | 为 true 表示由你的服务端、控制台或系统发出 |
收到的消息(message 事件、最近的消息)的 message_id 和 created_at 总是有值。
ChatroomMember
members() 的一项。
| 字段 | 类型 | 说明 |
|---|---|---|
username | string | 用户名 |
nickname | string | 昵称,没有时为空字符串 |
avatar_url | string | 头像地址,没有时为空字符串 |
role | string | owner / admin / member |
joined_at | string | 这一次进入的时间 |
ChatroomAttribute
一个属性。
| 字段 | 类型 | 说明 |
|---|---|---|
value | string | 值 |
owner | string | null | 最近一次设置这个键的用户;你的服务端设置的为 null |
auto_delete | boolean | 是否在设置人离开聊天室时自动删除 |
updated_at | string | 最近一次设置的时间 |
AttributeResult
setAttributes()、removeAttributes() 的结果。
| 字段 | 类型 | 说明 |
|---|---|---|
changed | boolean | 是否有键被写入或删除 |
attributes_version | number | 操作之后的属性版本号 |
failed | Record<string, { code: string; reason?: string }> | 失败的键:permission_denied(attribute_owned,键属于别人)或 limit_exceeded(attribute_limit,个数或总大小超出)。没有失败时为空对象 |
ChatroomMemberResult
批量管理操作中一个用户的结果,二者之一:
| 字段 | 类型 | 说明 |
|---|---|---|
username | string | 用户名 |
result | string | 成功时的结果:muted、unmuted、banned、unbanned、removed、not_in_room、added、unchanged |
code | string | 失败时的错误码 |
message | string | 失败时的说明 |
details | Record<string, unknown> | 失败的详细原因,如 { reason: 'owner_protected' } |
ChatroomPatch
manage.update() 的参数,见 handle.manage.update()。
