跳到正文
5 分钟

Presence

在文档中展示在线成员、协作者名称与头像,并启用远端光标和选区。

Presence 让用户看到谁打开了同一份文档,以及其他人正在操作哪里。例如,Bob 选中 B3 单元格时, Alice 能看到标有“Bob”的选区。你还可以在应用中用头像栏或成员列表展示当前协作者。

SDK 负责同步成员和光标信息,并渲染支持的远端光标。应用可以提供协作者的名称与头像, 并基于成员订阅 API 构建头像栏、成员面板或在线人数统计。

开始之前

先完成快速开始身份与权限。 此时,编辑器应已能同步内容修改,服务端通过 context.userID 识别登录用户,读取、JOIN 和编辑的 权限规则也已配置。

以下代码在已有的 transportendpointuniver 配置上继续添加功能,保留上一节的认证和 权限规则。

成员与光标同步

Alice 和 Bob 打开同一张表格。Presence 有两类更新:

操作成员列表Alice 在表格中看到的变化
Alice 打开表格Alice自己的选区
Bob 打开同一张表格Alice、BobBob 选中单元格时,出现他的选区
Bob 从 B3 移到 C5Alice、BobBob 的选区移动到 C5
Bob 关闭表格收到离开通知后,只剩 AliceBob 的选区消失

连接加入或离开时,成员列表会更新。移动光标改变的是编辑器中的选区位置,成员数量不变。 如果 Bob 进一步修改 C5 的内容,文档内容会通过协同编辑流程同步。

Presence 的范围是一份文档(一个 Unit)。它反映当前连接到这份文档的人;上一节配置的访问权限 列表则记录谁有权打开文档。Presence 是临时状态:移动光标不会生成 changeset、推进文档 revision, 也不会保存到数据库。

启用快速开始中的协同插件和对应编辑器 UI 后,SDK 会采集并发送本地光标与选区更新, 并在其他成员的编辑器中渲染对应的光标、选区和名称。

编辑器在线成员订阅远端光标与选区 UI
Sheet支持单元格与区域选区
Doc支持文本光标与选区
Slide支持指针位置与对象选中状态
Board支持指针位置与对象选中状态
Base支持不提供

1. 设置协作者的名称和头像

在 Endpoint 的 connect Middleware 中设置 context.member.namecontext.member.avatar。 资料由应用获取:既可以根据已认证的 context.session.userID 查询,也可以通过 customData 复用认证时取得的资料。

下面采用 customData 方式,避免重复查询。将这些字段补充到已有的 Transport Middleware 中, 并在启动服务前注册 connect

TypeScript
type AppUser = {  id: string;  name: string;  avatar?: string;};transport.use(async (context, next) => {  // 应用的认证函数;认证失败时拒绝请求。  const user: AppUser = await auth.requireUser(context.incomingMessage);  context.userID = user.id;  // 本例自定义的字段,用于在 connect 中复用资料。  context.customData.user = user;  await next();});endpoint.use("connect", async (context, next) => {  // 读取签发 Session Ticket 时保存的资料。  const user = context.session.customData.user as AppUser;  // 不设置时,成员名称默认使用 userID。  context.member.name = user.name;  // 没有头像 URL 时使用空字符串。  context.member.avatar = user.avatar ?? "";  await next();});

SDK 将签发 Session Ticket 的请求中的 customData 传递到 context.session.customData。 其中的 user 字段和 AppUser 类型由本例自行约定,SDK 不解析它们。其他协作者收到的是应用设置在 context.member 上的成员资料。

connect 在每次建立新的 WebSocket 连接时执行一次,重连也会执行,时机在客户端加入文档之前。 应用中修改用户资料后,不会自动更新已经共享给已有连接的成员资料。

2. 展示成员列表(可选)

如果应用需要头像栏或成员面板,使用 subscribeCollaborators(unitID, callback) 订阅已打开文档的 成员。光标渲染不依赖应用是否添加这个订阅。

TypeScript
import { FUniver } from "@univerjs/core/facade";// 在前端入口导入一次,启用协同 Facade 方法。import "@univerjs-pro/collaboration-client/facade";// 如果编辑器初始化时已提供 univerAPI,直接复用。const univerAPI = FUniver.newAPI(univer);// unitID 是编辑器中已打开文档的 ID。const subscription = univerAPI.getCollaboration().subscribeCollaborators(unitID, (members) => {  // 每次回调都是新的列表快照,应用可以用它更新 UI。  console.table(members);});

SDK 会等待文档的协同房间就绪。每次回调提供客户端当前已知成员的 IMember[] 快照,加入后也 包含自己;初始化期间可能收到空列表。回调反映成员变化,不包含光标位置。

Alice 和 Bob 在线时,成员数据包含以下字段(ID 仅作示意):

userIDmemberIDnameavatar
alice83446a72-a1cc-4ed8-aedc-afd8907bbb18Alice""
bob7ee5a94e-c784-4e1e-b1a3-b9d34be8ec4eBob""

userID 标识应用中的一个用户,memberID 由 SDK 分配,标识一次连接。如果 Bob 再打开一个 标签页,列表会增加一项:userID 仍为 "bob"memberID 则不同。此时有两个用户、三个连接。 应用可以按 userID 合并头像或统计在线用户数,也可以按 memberID 展示和统计连接。 编辑器按连接分别展示光标。

使用此订阅的视图关闭时,释放订阅:

TypeScript
subscription.dispose();

销毁编辑器时,再释放应用创建的 univerAPI 和 Univer 实例。如果只是关闭成员面板,释放该面板的 订阅即可。

示例:React 成员列表

将已有的 univerAPI 和文档 ID 传入这个组件:

TSX
import type { FUniver } from "@univerjs/core/facade";import type { IMember } from "@univerjs/protocol";import "@univerjs-pro/collaboration-client/facade";import { useEffect, useState } from "react";export function OnlineMembers({ univerAPI, unitID }: { univerAPI: FUniver; unitID: string }) {  const [members, setMembers] = useState<IMember[]>([]);  useEffect(() => {    // 等待新订阅数据期间,清空上一份文档的列表。    setMembers([]);    const subscription = univerAPI.getCollaboration().subscribeCollaborators(unitID, setMembers);    // 切换文档或卸载组件时取消监听。    return () => subscription.dispose();  }, [univerAPI, unitID]);  // 同一用户可能有多个连接,这里按 userID 合并显示。  const users = [...new Map(members.map((member) => [member.userID, member])).values()];  return (    <ul>      {users.map((user) => (        <li key={user.userID}>          {user.avatar && <img src={user.avatar} alt="" width={24} height={24} />}          <span>{user.name}</span>        </li>      ))}    </ul>  );}

成员订阅反映客户端最近收到的房间成员状态。连接丢失需要时间才能检测到,因此离开的成员可能 短暂保留。SDK 内置的协同状态 UI 提供当前客户端的同步状态、离线提示和重连入口。

用两个用户验证

  1. 在不同的浏览器用户配置中分别登录 Alice 和 Bob,打开同一份文档。
  2. 确认成员列表中出现两人的名称。
  3. 在 Sheet 中,用 Bob 先选中 B3,再选中 C5。Alice 应看到标有 Bob 名称的选区移动,成员列表保持不变。
  4. 用 Bob 再打开一个标签页。订阅数据应增加一个 memberID,React 列表仍只显示一次 Bob。
  5. 关闭 Bob 的两个标签页。收到离开通知后,Alice 的成员列表应只剩自己。