升级指南
从 3.x 升级到 4.0
Hocuspocus v4 带来了跨运行时支持(Bun、Deno、Cloudflare Workers、带 uWebSockets 的 Node)、通过泛型 Context 类型提升的类型安全,以及结构化的事务来源。
网络协议在两个方向上都保持向后兼容:v3 提供者可以连接到 v4 服务器,反之亦然。完整详情请参见 v4 发布说明。
1. 更新依赖
npm install @hocuspocus/server@^4.0.0 @hocuspocus/provider@^4.0.0Node.js 要求: v4 需要 Node.js 22 或更高版本。
如果你使用 SQLite 扩展,请将 sqlite3 替换为 better-sqlite3:
npm uninstall sqlite3
npm install @hocuspocus/extension-sqlite@^4.0.0 better-sqlite3
npm install -D @types/better-sqlite3现有的 SQLite 数据库文件完全兼容——无需迁移数据。
2. Web 标准的 Request 和 Headers(破坏性变更)
所有 hook 负载现在都使用 Web 标准的 Request 和 Headers 对象,而不是 Node.js 的 IncomingMessage 和 IncomingHttpHeaders。
之前(v3):
async onAuthenticate({ request, requestHeaders }) {
const token = requestHeaders['authorization']
const ip = requestHeaders['x-forwarded-for']
const url = request.url
}之后(v4):
async onAuthenticate({ request, requestHeaders }) {
const token = requestHeaders.get('authorization')
const ip = requestHeaders.get('x-forwarded-for')
const url = request.url
}request.socket.remoteAddress 不再可用——请改用来自反向代理的 x-forwarded-for 或 x-real-ip 请求头。
onUpgrade 和 onRequest hooks 仍然使用 Node.js 的 IncomingMessage/ServerResponse,因为它们在 WebSocket 升级之前于 HTTP 层运行。
3. onStoreDocument 负载(破坏性变更)
onStoreDocument 和 afterStoreDocument 的负载已重新结构化。与特定连接绑定的字段已被移除,因为 store hooks 现在也可能由非连接来源触发。
之前(v3):
async onStoreDocument({ context, requestHeaders, requestParameters, socketId, transactionOrigin, document, documentName, clientsCount, instance }) {
// ...
}之后(v4):
async onStoreDocument({ lastContext, lastTransactionOrigin, document, documentName, clientsCount, instance }) {
// `context` → `lastContext`
// `transactionOrigin` → `lastTransactionOrigin`
// `requestHeaders`, `requestParameters`, `socketId` — 已移除。如有需要,请通过 `lastContext` 访问。
}4. onAwarenessUpdate 负载(破坏性变更)
与连接相关的字段已移除,并替换为更新的来源。
之后(v4):
async onAwarenessUpdate({
transactionOrigin, // 新:结构化来源
connection, // 新:可选,触发更新的连接
document,
documentName,
added, updated, removed,
awareness, // 新:Awareness 实例
states,
}) {
// 如有需要,可通过 connection 访问 context
const context = connection?.context
}5. 事务来源(破坏性变更)
事务来源现在是结构化对象,而不是原始值。
之前(v3):
async onChange({ transactionOrigin }) {
if (transactionOrigin === '__hocuspocus__redis__origin__') {
// 来自 Redis
}
if (transactionOrigin instanceof Connection) {
// 来自客户端连接
}
}之后(v4):
import { isTransactionOrigin } from '@hocuspocus/server'
async onChange({ transactionOrigin }) {
if (isTransactionOrigin(transactionOrigin)) {
switch (transactionOrigin.source) {
case 'redis':
// 来自 Redis
break
case 'connection':
// transactionOrigin.connection 可用
break
case 'local':
// 来自服务端代码(例如 DirectConnection)
break
}
}
}6. WebSocket 选项(破坏性变更)
WebSocket 选项现在作为配置对象的一部分传入,而不是作为单独参数传入。
之前(v3):
const server = new Server(
{ port: 8080, extensions: [...] },
{ maxPayload: 1024 * 1024 }, // ws 选项作为第二个参数
)之后(v4):
const server = new Server({
port: 8080,
extensions: [...],
websocketOptions: { maxPayload: 1024 * 1024 },
})7. WebSocket 类型变更(破坏性变更)
如果你的代码引用了来自 ws 包的 WebSocket 类型,请改用 WebSocketLike:
import type { WebSocketLike } from '@hocuspocus/server'
const ws: WebSocketLike = connection.webSocketWebSocketLike 是一个最小接口,包含 send、close 和 readyState —— 与所有受支持的运行时兼容。
8. 自定义 handleConnection 集成(破坏性变更)
如果你直接调用 handleConnection()(例如用于 Express/Koa 集成),其签名已更改:
之后(v4):
wss.on('connection', (ws, request: Request) => {
const clientConnection = hocuspocus.handleConnection(ws, request, context)
// 现在会返回 clientConnection,便于程序化访问
})request必须是 Web 标准的Request(不是IncomingMessage)- 该方法现在会返回一个
ClientConnection实例 - WebSocket 不再需要来自
ws包——任何WebSocketLike都可以 - 如果你没有使用内置的
Server类,你需要负责调用clientConnection.handleMessage(data)和clientConnection.handleClose(event)
9. Provider CloseEvent 形状(破坏性变更)
传递给 onClose 回调的 CloseEvent 不再包含 target 和 type。只保留 code 和 reason。
onClose({ event }) {
console.log(event.code, event.reason)
// event.target 和 event.type 不再可用
}10. Provider ws 包类型已移除(仅 TypeScript)
provider 不再从 ws 包重新导出 Event、MessageEvent 或 CloseEvent。请从 @hocuspocus/common 导入它们,或者改用 Web 标准类型。
11. 超时变更(非破坏性变更)
默认连接超时时间已从 30 秒增加到 60 秒。若要保持旧行为:
const server = new Server({
timeout: 30_000,
})总结清单
服务器
- 升级到 Node.js 22+
- 将所有
@hocuspocus/*包升级到 v4 - 在所有 hooks 中将
requestHeaders['key']替换为requestHeaders.get('key') - 将
request.socket.remoteAddress替换为代理请求头(x-forwarded-for) - 更新
onStoreDocument处理器:context→lastContext,移除requestHeaders/requestParameters/socketId - 如果使用了
onAwarenessUpdate,请更新其处理器 - 将来自
ws的WebSocket类型导入替换为@hocuspocus/server中的WebSocketLike - 将
websocketOptions移入Server配置对象 - 更新事务来源检查,改用
isTransactionOrigin()和.source - 如果使用 SQLite:将
sqlite3替换为better-sqlite3 - 如果使用自定义
handleConnection:更新为新签名和Request类型
Provider
- 将
@hocuspocus/provider升级到 v4 - 移除
onClose处理器中对event.target/event.type的引用 - 更新依赖于 provider 中
ws类型的 TypeScript 导入
从 2.x 升级到 3.0(Provider)
TiptapCollabProvider 已移至 @tiptap-pro/provider 包。
在使用多路复用时,提供者现在需要显式调用 attach 来附加到 websocket。
import {
TiptapCollabProvider,
TiptapCollabProviderWebsocket
} from "@tiptap-pro/provider";
const socket = new TiptapCollabProviderWebsocket({
appId: '', // 或者如果使用 `HocuspocusProviderWebsocket` 则使用 `url`
})
const provider1 = new TiptapCollabProvider({
websocketProvider: socket,
name: 'document1',
token: '',
})
provider1.attach() // 当手动传入 socket 时,需要显式调用 attach更多信息请参见:https://github.com/ueberdosis/hocuspocus/releases/tag/v3.1.0
从 2.x 升级到 3.0(服务器端)
升级到新版本后,hocuspocus 的初始化方式发生了变化。如 使用说明 所述, 使用 hocuspocus 有两种方式:内置服务器,或作为库用于其他框架(如 express)。 为了简化操作并支持更多未来功能,我们将类进行了分离,并把服务器放入了它自己的类中。
使用 .configure() 的方式变更
不再支持使用 .configure() 来使用 hocuspocus。你现在必须自己创建一个新的实例。
旧方式
import { Server } from "@hocuspocus/server";
const server = Server.configure({
port: 1234,
});
server.listen();新方式
import { Server } from "@hocuspocus/server";
const server = new Server({
port: 1234,
});
server.listen();注意,导入方式未变,配置选项也保持不变。
不使用内置服务器的 Hocuspocus 用法
如果之前使用过不带内置服务器的 Hocuspocus,也需要更新你的设置。
旧方式
import { Server } from "@hocuspocus/server";
const server = Server.configure({
// ...
});新方式
import { Hocuspocus } from "@hocuspocus/server";
const hocuspocus = new Hocuspocus({
// ...
});
// 你仍然可以像以前一样使用 handleConnection。
hocuspocus.handleConnection(...);注意导入从 Server 改为 Hocuspocus,并通过 new Hocuspocus() 进行初始化。
更多内容请参阅 示例。
服务器 listen 方法签名的变更
服务器的 .listen() 方法曾经非常灵活,我们简化了它的签名,但仍可达到之前相同的行为。
旧签名
async listen(
portOrCallback: number | ((data: onListenPayload) => Promise<any>) | null = null,
callback: any = null,
): Promise<Hocuspocus>新签名
async listen(port?: number, callback: any = null): Promise<Hocuspocus>listen 方法依然返回一个 Promise,成功时解析为 Hocuspocus。
旧版本中你能传入的两种回调现在都合并到了 onListen 钩子中。新版本中的回调参数依然有效,
但你无法再只把回调函数放在第一个参数。如果你只想添加回调,也可以将其配置在服务器初始化时。
import { Server } from "@hocuspocus/server";
const server = new Server({
async onListen(data) {
console.log(`服务器已在端口 "${data.port}" 上监听!`);
},
});
server.listen()