guanghulab/server/app/modules/agent-handshake.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

151 lines
4.0 KiB
JavaScript

'use strict';
const crypto = require('crypto');
const axios = require('axios');
const { validateSession } = require('./email-auth');
// ─── 常量 ───
const HANDSHAKE_EXPIRE_MS = 5 * 60 * 1000; // 5分钟
const HEARTBEAT_INTERVAL = 30 * 1000; // 30秒
const MAX_RETRIES = 3;
// ─── 握手状态存储 ───
const pendingHandshakes = new Map(); // token → { agentId, publicKey, createdAt }
const activeConnections = new Map(); // connectionId → { agentId, partnerId, heartbeatAt }
// ─── 错误代码 ───
const ERRORS = {
INVALID_SESSION: { code: 'HS001', message: '无效会话' },
INVALID_AGENT_ID: { code: 'HS002', message: '无效Agent ID' },
HANDSHAKE_EXPIRED: { code: 'HS003', message: '握手过期' },
ENCRYPTION_FAILED: { code: 'HS004', message: '加密失败' },
DECRYPTION_FAILED: { code: 'HS005', message: '解密失败' },
INVALID_SIGNATURE: { code: 'HS006', message: '无效签名' },
CONNECTION_LOST: { code: 'HS007', message: '连接丢失' }
};
/**
* 生成密钥对
*/
function generateKeyPair() {
return crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});
}
/**
* 初始化握手
* @param {string} token - 会话token
* @param {string} agentId - Agent ID
* @returns {{ handshakeId: string, publicKey: string }}
*/
function initHandshake(token, agentId) {
if (!validateSession(token).valid) {
throw ERRORS.INVALID_SESSION;
}
if (!agentId || typeof agentId !== 'string') {
throw ERRORS.INVALID_AGENT_ID;
}
const handshakeId = crypto.randomBytes(16).toString('hex');
const keyPair = generateKeyPair();
pendingHandshakes.set(handshakeId, {
agentId,
publicKey: keyPair.publicKey,
privateKey: keyPair.privateKey,
createdAt: Date.now()
});
return { handshakeId, publicKey: keyPair.publicKey };
}
/**
* 完成握手
* @param {string} handshakeId - 握手ID
* @param {string} partnerId - 对方Agent ID
* @param {string} encryptedData - 加密数据
* @returns {{ connectionId: string, partnerPublicKey: string }}
*/
function completeHandshake(handshakeId, partnerId, encryptedData) {
const handshake = pendingHandshakes.get(handshakeId);
if (!handshake) {
throw ERRORS.INVALID_SESSION;
}
if (Date.now() - handshake.createdAt > HANDSHAKE_EXPIRE_MS) {
pendingHandshakes.delete(handshakeId);
throw ERRORS.HANDSHAKE_EXPIRED;
}
try {
const decrypted = crypto.privateDecrypt(
{
key: handshake.privateKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
},
Buffer.from(encryptedData, 'base64')
).toString('utf8');
const { publicKey: partnerPublicKey } = JSON.parse(decrypted);
const connectionId = crypto.randomBytes(16).toString('hex');
activeConnections.set(connectionId, {
agentId: handshake.agentId,
partnerId,
heartbeatAt: Date.now()
});
pendingHandshakes.delete(handshakeId);
return { connectionId, partnerPublicKey };
} catch (err) {
console.error(`[Agent握手] 解密失败: ${err.message}`);
throw ERRORS.DECRYPTION_FAILED;
}
}
/**
* 发送心跳
* @param {string} connectionId - 连接ID
*/
function sendHeartbeat(connectionId) {
const connection = activeConnections.get(connectionId);
if (!connection) {
throw ERRORS.INVALID_SESSION;
}
connection.heartbeatAt = Date.now();
}
/**
* 清理过期握手和连接
*/
function cleanupExpired() {
const now = Date.now();
for (const [handshakeId, handshake] of pendingHandshakes.entries()) {
if (now - handshake.createdAt > HANDSHAKE_EXPIRE_MS) {
pendingHandshakes.delete(handshakeId);
}
}
for (const [connectionId, connection] of activeConnections.entries()) {
if (now - connection.heartbeatAt > HEARTBEAT_INTERVAL * 3) {
activeConnections.delete(connectionId);
}
}
}
// 每1分钟清理一次
setInterval(cleanupExpired, 60 * 1000);
module.exports = {
initHandshake,
completeHandshake,
sendHeartbeat,
ERRORS
};