2024-12-30 08:26:40 +08:00
|
|
|
|
/**
|
|
|
|
|
* 此模块实现了一个回调处理系统,用于在协同编辑文档发生更改时通知外部服务。
|
|
|
|
|
* 它支持多种共享数据类型(Array、Map、Text、XML等)的同步,并可以将更新通过HTTP POST请求发送到指定的回调URL。
|
|
|
|
|
* 主要用于与外部系统集成,实现文档变更的实时通知。
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import http from 'http';
|
|
|
|
|
import { parseInt as libParseInt } from 'lib0/number';
|
|
|
|
|
import { WSSharedDoc } from './ws-shared-doc';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 回调URL配置,从环境变量中获取
|
|
|
|
|
* 如果环境变量未设置则为null
|
|
|
|
|
*/
|
2025-03-31 20:44:33 +08:00
|
|
|
|
const CALLBACK_URL = process.env.CALLBACK_URL
|
|
|
|
|
? new URL(process.env.CALLBACK_URL)
|
|
|
|
|
: null;
|
2024-12-30 08:26:40 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 回调超时时间配置,从环境变量中获取
|
|
|
|
|
* 默认为5000毫秒
|
|
|
|
|
*/
|
|
|
|
|
const CALLBACK_TIMEOUT = libParseInt(process.env.CALLBACK_TIMEOUT || '5000');
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 需要监听变更的共享对象配置
|
|
|
|
|
* 从环境变量CALLBACK_OBJECTS中解析JSON格式的配置
|
|
|
|
|
*/
|
2025-03-31 20:44:33 +08:00
|
|
|
|
const CALLBACK_OBJECTS: Record<string, string> = process.env.CALLBACK_OBJECTS
|
|
|
|
|
? JSON.parse(process.env.CALLBACK_OBJECTS)
|
|
|
|
|
: {};
|
2024-12-30 08:26:40 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 导出回调URL是否已配置的标志
|
|
|
|
|
*/
|
|
|
|
|
export const isCallbackSet = !!CALLBACK_URL;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 定义要发送的数据结构接口
|
|
|
|
|
*/
|
|
|
|
|
interface DataToSend {
|
|
|
|
|
room: string; // 房间/文档标识
|
2025-03-31 20:44:33 +08:00
|
|
|
|
data: Record<
|
|
|
|
|
string,
|
|
|
|
|
{
|
|
|
|
|
type: string; // 数据类型
|
|
|
|
|
content: any; // 数据内容
|
|
|
|
|
}
|
|
|
|
|
>;
|
2024-12-30 08:26:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 定义更新数据的类型
|
|
|
|
|
*/
|
|
|
|
|
type UpdateType = Uint8Array;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 定义更新来源的类型
|
|
|
|
|
*/
|
|
|
|
|
type OriginType = any;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理文档更新的回调函数
|
|
|
|
|
* @param update - 更新的数据
|
|
|
|
|
* @param origin - 更新的来源
|
|
|
|
|
* @param doc - 共享文档实例
|
|
|
|
|
*/
|
2025-03-31 20:44:33 +08:00
|
|
|
|
export const callbackHandler = (
|
|
|
|
|
update: UpdateType,
|
|
|
|
|
origin: OriginType,
|
|
|
|
|
doc: WSSharedDoc,
|
|
|
|
|
): void => {
|
2024-12-30 08:26:40 +08:00
|
|
|
|
// 获取文档名称作为房间标识
|
|
|
|
|
const room = doc.name;
|
2025-03-31 20:44:33 +08:00
|
|
|
|
|
2024-12-30 08:26:40 +08:00
|
|
|
|
// 初始化要发送的数据对象
|
|
|
|
|
const dataToSend: DataToSend = {
|
|
|
|
|
room,
|
2025-03-31 20:44:33 +08:00
|
|
|
|
data: {},
|
2024-12-30 08:26:40 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 获取所有需要监听的共享对象名称
|
|
|
|
|
const sharedObjectList = Object.keys(CALLBACK_OBJECTS);
|
2025-03-31 20:44:33 +08:00
|
|
|
|
|
2024-12-30 08:26:40 +08:00
|
|
|
|
// 遍历所有共享对象,获取它们的最新内容
|
2025-03-31 20:44:33 +08:00
|
|
|
|
sharedObjectList.forEach((sharedObjectName) => {
|
2024-12-30 08:26:40 +08:00
|
|
|
|
const sharedObjectType = CALLBACK_OBJECTS[sharedObjectName];
|
|
|
|
|
dataToSend.data[sharedObjectName] = {
|
|
|
|
|
type: sharedObjectType,
|
2025-03-31 20:44:33 +08:00
|
|
|
|
content: getContent(sharedObjectName, sharedObjectType, doc).toJSON(),
|
2024-12-30 08:26:40 +08:00
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 如果配置了回调URL,则发送HTTP请求
|
|
|
|
|
if (CALLBACK_URL) {
|
|
|
|
|
callbackRequest(CALLBACK_URL, CALLBACK_TIMEOUT, dataToSend);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 发送HTTP回调请求
|
|
|
|
|
* @param url - 回调的目标URL
|
|
|
|
|
* @param timeout - 超时时间
|
|
|
|
|
* @param data - 要发送的数据
|
|
|
|
|
*/
|
|
|
|
|
const callbackRequest = (url: URL, timeout: number, data: DataToSend): void => {
|
|
|
|
|
// 将数据转换为JSON字符串
|
|
|
|
|
const dataString = JSON.stringify(data);
|
|
|
|
|
|
|
|
|
|
// 配置HTTP请求选项
|
|
|
|
|
const options = {
|
|
|
|
|
hostname: url.hostname,
|
|
|
|
|
port: url.port,
|
|
|
|
|
path: url.pathname,
|
|
|
|
|
timeout,
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
2025-03-31 20:44:33 +08:00
|
|
|
|
'Content-Length': Buffer.byteLength(dataString),
|
|
|
|
|
},
|
2024-12-30 08:26:40 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 创建HTTP请求
|
|
|
|
|
const req = http.request(options);
|
|
|
|
|
|
|
|
|
|
// 处理超时事件
|
|
|
|
|
req.on('timeout', () => {
|
|
|
|
|
console.warn('Callback request timed out.');
|
|
|
|
|
req.abort();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 处理错误事件
|
|
|
|
|
req.on('error', (e) => {
|
|
|
|
|
console.error('Callback request error.', e);
|
|
|
|
|
req.abort();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 发送数据
|
|
|
|
|
req.write(dataString);
|
|
|
|
|
req.end();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据对象类型获取共享对象的内容
|
|
|
|
|
* @param objName - 对象名称
|
|
|
|
|
* @param objType - 对象类型
|
|
|
|
|
* @param doc - 共享文档实例
|
|
|
|
|
* @returns 共享对象的内容
|
|
|
|
|
*/
|
2025-03-31 20:44:33 +08:00
|
|
|
|
const getContent = (
|
|
|
|
|
objName: string,
|
|
|
|
|
objType: string,
|
|
|
|
|
doc: WSSharedDoc,
|
|
|
|
|
): any => {
|
2024-12-30 08:26:40 +08:00
|
|
|
|
// 根据对象类型返回相应的共享对象
|
|
|
|
|
switch (objType) {
|
2025-03-31 20:44:33 +08:00
|
|
|
|
case 'Array':
|
|
|
|
|
return doc.getArray(objName);
|
|
|
|
|
case 'Map':
|
|
|
|
|
return doc.getMap(objName);
|
|
|
|
|
case 'Text':
|
|
|
|
|
return doc.getText(objName);
|
|
|
|
|
case 'XmlFragment':
|
|
|
|
|
return doc.getXmlFragment(objName);
|
|
|
|
|
case 'XmlElement':
|
|
|
|
|
return doc.getXmlElement(objName);
|
|
|
|
|
default:
|
|
|
|
|
return {};
|
2024-12-30 08:26:40 +08:00
|
|
|
|
}
|
|
|
|
|
};
|