共同作業:誰がいるか、ライブカーソル、編集ロック
同じドキュメントやチケットを見ている全員のアバターを表示し、カーソルとフォーカスを共有、保存した変更を配信して、誰かの編集を上書きすることを防ぎます。
2 人が知らずに同じレコードを編集すると、どちらかの作業が消えてしまいます。同じドキュメント、チケット、デザインを誰が見ているか、どこにいるかを表示すれば、それを防げるうえに、プロダクトに「一緒に使っている」感覚が生まれます。プロジェクト管理ツール、CRM、ヘルプデスク、ホワイトボード、フォームビルダーなどで役立ちます。
ユーザーにとっての価値
- 同じページを見ている全員のアバター。人の出入りに合わせて更新されます。
- ライブカーソルや、誰かが編集中のフィールドのハイライト。
- 保存の衝突ではなく、はっきりした「Ann がこのチケットを編集中」のロック表示。
- 他の人の変更が、再読み込みなしでその場に反映されます。
使う機能
- プレゼンスチャンネル(
presence-)。ドキュメントごとに 1 つ、メンバー一覧はすべてのノードで正しく保たれます。 - クライアントイベント(
client-*)。カーソル位置やフォーカスをブラウザ間で送ります。 - サーバーイベント。保存した変更を、サーバーが保存してから配信します。
- ラージプレゼンスチャンネル(
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 つのタブで開いているユーザーは 1 人として数えます。最初のソケットで追加され、最後のソケットが閉じたときに削除されます。
ステップ 2:カーソルとフォーカスを共有する
アプリの設定で クライアントイベントを許可 をオンにします。位置は変わったときだけ、1 秒に数回までに抑えて送ります:
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)));プレゼンスチャンネルでは、クライアントイベントのハンドラーの 2 番目の引数に送信者の 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 人を超える閲覧者
プレゼンスチャンネルのメンバーは最大 100 人です。全員が一覧全体を受け取るためです。全社向けのお知らせやライブイベントのように多くの人が見るページでは、presence-large-doc-<id> と @hibira-io/large-presence を使いましょう。最大 100,000 人まで入れられ、参加のたびのイベントではなく人数とサンプルを送ります。そこではクライアントイベントは使えないので、カーソルの共有は小さなルームだけにします。
ヒント
- クライアントイベントは 1 件ごとに 1 メッセージです。10 人の編集者が 1 秒に 5 回カーソルを送れば毎秒 50 メッセージなので、変化したときだけ送り、タブが非表示のときは頻度を下げましょう。
- 正となるデータはサーバーに置きます。クライアントイベントはカーソルやフォーカスのようなヒント用で、保存されず、送信者には返りません。
- 本格的な共同テキスト編集には、Yjs や Automerge などの CRDT ライブラリの操作を、同じようにサーバー経由で送ります。