You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

在Hocuspocus服务器恢复Y.js文档快照无法同步至Tiptap客户端

解决Hocuspocus服务器恢复Y.js快照后客户端不同步的问题

问题诊断

客户端能收到restored广播消息但编辑器内容不更新,说明服务器端的文档更新未被Hocuspocus正确捕获并同步给客户端。核心原因是直接操作底层Y.Doc的更新未触发Hocuspocus的同步机制,且清空文档的方式存在逻辑问题。

修复方案

1. 改用Hocuspocus官方API处理更新

放弃直接调用Y.applyUpdate操作Y.Doc,使用Hocuspocus提供的docWrapper.applyUpdate方法,确保服务器端的更新被框架追踪并自动广播给所有客户端。

2. 正确清空现有文档内容

替换原有用空Y.Doc生成update的方式,直接遍历并删除当前文档的所有根节点,避免不必要的状态冲突。

3. 明确更新来源标记

在应用快照时指定更新来源为服务器,确保Hocuspocus不会忽略该更新的同步逻辑。

修改后的代码

async onStateless({ documentName, payload, document }) {
  const data = JSON.parse(payload as string);
  logger.info(`Stateless message for ${documentName}:`, data);

  if (data.type !== 'restore') return;

  // 从数据库获取快照
  const snapshotResult = await pgPool.query(
    `SELECT id, state, created_by, created_at, label, metadata
     FROM yjs_snapshot
     WHERE id = $1`,
    [data.snapshotId]
  );

  if (snapshotResult.rows.length === 0) {
    logger.warn(`Snapshot ${data.snapshotId} not found for ${documentName}`);
    return;
  }

  const snapshot = snapshotResult.rows[0];
  const docWrapper = hocuspocus.documents.get(documentName);
  if (!docWrapper) return;

  try {
    // 清空当前文档的所有根节点
    document.transact(() => {
      for (const key of Array.from(document.keys())) {
        document.delete(key);
      }
    });

    // 解析快照并通过Hocuspocus API应用更新
    const snapshotBuffer = new Uint8Array(snapshot.state);
    await docWrapper.applyUpdate(snapshotBuffer, {
      origin: 'server' // 标记为服务器发起的全局更新
    });

    // 发送恢复完成的广播
    docWrapper.broadcastStateless(
      JSON.stringify({
        type: 'restored',
        by: data.by,
        at: new Date().toISOString(),
        info: data.info || null,
        snapshotId: data.snapshotId,
      })
    );

    logger.info(`Document ${documentName} restored from snapshot ${data.snapshotId}`);
  } catch (error) {
    logger.error(`Failed to restore snapshot ${data.snapshotId} for ${documentName}:`, error);
  }
}

关键修改点说明

  • 使用docWrapper.applyUpdate:Hocuspocus的DocumentWrapper会在应用更新后自动触发同步逻辑,确保所有连接的客户端收到最新文档状态。
  • 直接删除根节点清空文档:避免了空Y.Doc update可能带来的状态冲突,更高效且符合Y.js的文档操作规范。
  • 指定origin: 'server':明确标记这是服务器发起的全局更新,Hocuspocus不会跳过该更新的广播流程。
  • 增加错误捕获:便于排查恢复过程中出现的异常,提升代码健壮性。

内容的提问来源于stack exchange,提问作者Alex Kleshchevnikov

火山引擎 最新活动