← 使用场景

协作:谁在这里、实时光标与编辑锁定

显示正在看同一份文档或工单的每个人的头像、共享光标与焦点,并广播已保存的更改,谁都不会覆盖别人的修改。

两个人在不知情的情况下编辑同一条数据,总有一个人的心血会被覆盖。显示还有谁在看同一份文档、工单或设计稿,以及他们在哪里,就能避免这件事,也让产品有“一起做事”的感觉。项目管理工具、CRM、客服系统、白板和表单创建工具都用得上。

用户得到什么

  • 正在看同一页的每个人的头像,有人来去时跟着更新。
  • 实时光标,或在别人正在编辑的字段上标示出来。
  • 清楚的“Ann 正在编辑这张工单”锁定提示,而不是互相冲突的保存。
  • 别人的修改直接出现在画面上,不必重新加载。

用到的功能

  • Presence 频道(presence-),每份文档一个,成员名单在每个节点上都保持正确。
  • 客户端事件(client-*),在浏览器之间发送光标位置与焦点。
  • 服务器事件,在你的服务器保存修改之后才发布。
  • 大型 presence 频道(presence-large-),用在同时可能有超过 100 人观看的页面。

步骤 1:显示谁在这里

用检查文档访问权的同一套方式授权 presence-doc-<id>,并以 user_info 回传名字与头像(路由写法请看聊天与输入中提示)。接着:

const doc = pusher.subscribe('presence-doc-' + docId);

doc.bind('pusher:subscription_succeeded', (members) => avatars.render(members));
doc.bind('pusher:member_added', () => avatars.render(doc.members));
doc.bind('pusher:member_removed', (m) => {
  avatars.render(doc.members);
  cursors.remove(m.id);
});

同一位用户开了两个标签页只算一次:第一个连接加入时添加,最后一个连接关闭时移除。

步骤 2:分享光标与焦点

在应用设置里打开 允许客户端事件。位置每秒最多送几次,而且只在有变化时才送:

let pending = null;
document.addEventListener('pointermove', (e) => {
  pending = { x: e.pageX / document.body.scrollWidth, y: e.pageY };
});
setInterval(() => {
  if (!pending) return;
  doc.trigger('client-cursor', pending);
  pending = null;
}, 200);

doc.bind('client-cursor', (pos, meta) => cursors.move(meta.user_id, pos));

// Field locks: who is editing what
field.addEventListener('focus', () => doc.trigger('client-focus', { field: field.name }));
doc.bind('client-focus', ({ field }, meta) => locks.show(field, doc.members.get(meta.user_id)));

在 presence 频道上,客户端事件处理函数的第二个参数带有发送者的 user_id。

步骤 3:广播已保存的修改

有人保存时,先存下修改,再发布给其他人:

app.patch('/docs/:id', requireLogin, async (req, res) => {
  const change = await db.docs.apply(req.params.id, req.user.id, req.body.patch);
  await pusher.trigger('presence-doc-' + req.params.id, 'changed', { version: change.version, patch: req.body.patch, by: req.user.id }, { socket_id: req.body.socket_id });
  res.json(change);
});

其他观看者套用这份修改;如果自己的版本落后,就重新加载数据。

超过 100 位观看者

Presence 频道最多 100 名成员,因为每个人都会收到完整名单。很多人同时在看的页面,例如全公司公告或直播活动,请改用 presence-large-doc-<id> 搭配 @hibira-io/large-presence:最多可容纳 100,000 名成员,发送的是人数与成员样本,而不是每一次加入。那里不允许客户端事件,所以光标只在较小的房间里分享。

小提醒

  • 每个客户端事件都算一条消息。十位编辑者每秒各送五次光标,就是每秒 50 条消息,所以有变化才送,标签页隐藏时放慢。
  • 数据的正本留在你的服务器上。客户端事件是用来传光标、焦点这类提示的;它们不会被保存,也不会回传给发送者。
  • 要做完整的协作文本编辑,用同样的方式通过服务器发送 Yjs 或 Automerge 这类 CRDT 库的操作。