← 使用情境

協作:誰在這裡、即時游標與編輯鎖定

顯示正在看同一份文件或工單的每個人的頭像、分享游標與焦點,並廣播已儲存的變更,誰都不會蓋掉別人的修改。

兩個人在不知情的情況下編輯同一筆資料,總有一個人的心血會被蓋掉。顯示還有誰在看同一份文件、工單或設計稿,以及他們在哪裡,就能避免這件事,也讓產品有「一起做事」的感覺。專案管理工具、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 函式庫的操作。