Files
bigwo/test2/server/services/realtimeDialogProtocol.js

219 lines
5.8 KiB
JavaScript

const HEADER_SIZE_4 = 0x1;
const VERSION_1 = 0x10;
const SERIALIZATION_JSON = 0x1 << 4;
const SERIALIZATION_RAW = 0;
const COMPRESSION_NONE = 0;
const MSG_TYPE_FLAG_WITH_EVENT = 0b100;
const MsgType = {
INVALID: 0,
FULL_CLIENT: 1,
AUDIO_ONLY_CLIENT: 2,
FULL_SERVER: 9,
AUDIO_ONLY_SERVER: 11,
FRONT_END_RESULT_SERVER: 12,
ERROR: 15,
};
function getMessageTypeName(value) {
return Object.keys(MsgType).find((key) => MsgType[key] === value) || 'INVALID';
}
function containsEvent(typeFlag) {
return (typeFlag & MSG_TYPE_FLAG_WITH_EVENT) === MSG_TYPE_FLAG_WITH_EVENT;
}
function shouldHandleSessionId(event) {
return event !== 1 && event !== 2 && event !== 50 && event !== 51 && event !== 52;
}
function writeInt(buffer, value, offset) {
buffer.writeInt32BE(value, offset);
return offset + 4;
}
function writeStringWithLength(buffer, value, offset) {
const strBuffer = Buffer.from(value || '', 'utf8');
offset = writeInt(buffer, strBuffer.length, offset);
strBuffer.copy(buffer, offset);
return offset + strBuffer.length;
}
function writePayload(buffer, payload, offset) {
const payloadBuffer = Buffer.isBuffer(payload) ? payload : Buffer.from(payload || '');
offset = writeInt(buffer, payloadBuffer.length, offset);
payloadBuffer.copy(buffer, offset);
return offset + payloadBuffer.length;
}
function buildHeader(type, typeFlag, serialization) {
return Buffer.from([
VERSION_1 | HEADER_SIZE_4,
((type & 0x0f) << 4) | (typeFlag & 0x0f),
serialization | COMPRESSION_NONE,
0,
]);
}
function marshal(message, { rawPayload = false } = {}) {
const type = message.type;
const typeFlag = message.typeFlag || MSG_TYPE_FLAG_WITH_EVENT;
const payload = Buffer.isBuffer(message.payload) ? message.payload : Buffer.from(message.payload || '');
const serialization = rawPayload ? SERIALIZATION_RAW : SERIALIZATION_JSON;
let size = 4;
if (containsEvent(typeFlag)) {
size += 4;
}
if (containsEvent(typeFlag) && shouldHandleSessionId(message.event)) {
size += 4 + Buffer.byteLength(message.sessionId || '', 'utf8');
}
size += 4 + payload.length;
const buffer = Buffer.allocUnsafe(size);
buildHeader(type, typeFlag, serialization).copy(buffer, 0);
let offset = 4;
if (containsEvent(typeFlag)) {
offset = writeInt(buffer, message.event || 0, offset);
}
if (containsEvent(typeFlag) && shouldHandleSessionId(message.event)) {
offset = writeStringWithLength(buffer, message.sessionId || '', offset);
}
writePayload(buffer, payload, offset);
return buffer;
}
function readStringWithLength(buffer, offsetObj) {
const size = buffer.readInt32BE(offsetObj.offset);
offsetObj.offset += 4;
if (size <= 0) {
return '';
}
const value = buffer.subarray(offsetObj.offset, offsetObj.offset + size).toString('utf8');
offsetObj.offset += size;
return value;
}
function readPayload(buffer, offsetObj) {
const size = buffer.readInt32BE(offsetObj.offset);
offsetObj.offset += 4;
if (size <= 0) {
return Buffer.alloc(0);
}
const payload = buffer.subarray(offsetObj.offset, offsetObj.offset + size);
offsetObj.offset += size;
return payload;
}
function unmarshal(data) {
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
if (buffer.length < 4) {
throw new Error('protocol message too short');
}
const typeAndFlag = buffer[1];
const type = (typeAndFlag >> 4) & 0x0f;
const typeFlag = typeAndFlag & 0x0f;
const offsetObj = { offset: 4 };
const message = {
type,
typeName: getMessageTypeName(type),
typeFlag,
event: 0,
sessionId: '',
payload: Buffer.alloc(0),
};
if (containsEvent(typeFlag)) {
message.event = buffer.readInt32BE(offsetObj.offset);
offsetObj.offset += 4;
}
if (containsEvent(typeFlag) && shouldHandleSessionId(message.event)) {
message.sessionId = readStringWithLength(buffer, offsetObj);
}
message.payload = readPayload(buffer, offsetObj);
return message;
}
function createStartConnectionMessage() {
return marshal({
type: MsgType.FULL_CLIENT,
typeFlag: MSG_TYPE_FLAG_WITH_EVENT,
event: 1,
payload: Buffer.from('{}', 'utf8'),
});
}
function createStartSessionMessage(sessionId, payload) {
return marshal({
type: MsgType.FULL_CLIENT,
typeFlag: MSG_TYPE_FLAG_WITH_EVENT,
event: 100,
sessionId,
payload: Buffer.from(JSON.stringify(payload), 'utf8'),
});
}
function createAudioMessage(sessionId, audioBuffer) {
return marshal({
type: MsgType.AUDIO_ONLY_CLIENT,
typeFlag: MSG_TYPE_FLAG_WITH_EVENT,
event: 200,
sessionId,
payload: Buffer.isBuffer(audioBuffer) ? audioBuffer : Buffer.from(audioBuffer),
}, { rawPayload: true });
}
function createChatTTSTextMessage(sessionId, payload) {
return marshal({
type: MsgType.FULL_CLIENT,
typeFlag: MSG_TYPE_FLAG_WITH_EVENT,
event: 500,
sessionId,
payload: Buffer.from(JSON.stringify({
session_id: sessionId,
start: !!payload.start,
end: !!payload.end,
content: payload.content || '',
}), 'utf8'),
});
}
function createSayHelloMessage(sessionId, content) {
return marshal({
type: MsgType.FULL_CLIENT,
typeFlag: MSG_TYPE_FLAG_WITH_EVENT,
event: 300,
sessionId,
payload: Buffer.from(JSON.stringify({
content: content || '',
}), 'utf8'),
});
}
function createChatRAGTextMessage(sessionId, externalRag) {
return marshal({
type: MsgType.FULL_CLIENT,
typeFlag: MSG_TYPE_FLAG_WITH_EVENT,
event: 502,
sessionId,
payload: Buffer.from(JSON.stringify({
session_id: sessionId,
external_rag: externalRag || '[]',
}), 'utf8'),
});
}
module.exports = {
MsgType,
MSG_TYPE_FLAG_WITH_EVENT,
unmarshal,
createStartConnectionMessage,
createStartSessionMessage,
createAudioMessage,
createChatTTSTextMessage,
createSayHelloMessage,
createChatRAGTextMessage,
};