guanghulab/server/app/modules/agent-handshake.js
铸渊 ICE-GL-ZY001 f418aec5d8
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled
cz: D119 temporal-brain同步 · 衔光频道首次推送 · glada日志归档
2026-06-03 11:45:50 +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
};