anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
294 lines • 11.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommunicationManager = void 0;
const types_1 = require("./types");
const message_protocol_1 = require("./message-protocol");
const message_handler_1 = require("./message-handler");
class CommunicationManager {
constructor(agentIdentity, agentManager, delegationManager, policyEngine, activityLogger, options = {}) {
this.agentIdentity = agentIdentity;
this.channels = new Map();
this.stats = new Map();
this.pendingMessages = new Map();
this.options = {
enableEncryption: options.enableEncryption || false,
defaultMessageTTL: options.defaultMessageTTL || 300000, // 5 minutes
maxRetries: options.maxRetries || 3,
retryDelay: options.retryDelay || 5000,
enableStats: options.enableStats || true
};
this.messageHandlers = new message_handler_1.MessageHandlerRegistry(agentManager, delegationManager, policyEngine, activityLogger);
this.initializeStats();
}
/**
* Adds a communication channel
*/
addChannel(channel) {
this.channels.set(channel.id, channel);
// Set up message handling for this channel
channel.onMessage(async (envelope) => {
await this.handleIncomingMessage(envelope, channel.id);
});
// Initialize stats for this channel
if (this.options.enableStats) {
this.stats.set(channel.id, {
messagesSent: 0,
messagesReceived: 0,
messagesDropped: 0,
averageResponseTime: 0,
errorRate: 0
});
}
}
/**
* Removes a communication channel
*/
async removeChannel(channelId) {
const channel = this.channels.get(channelId);
if (channel) {
await channel.disconnect();
this.channels.delete(channelId);
this.stats.delete(channelId);
}
}
/**
* Connects all channels
*/
async connectAll() {
const connections = Array.from(this.channels.values()).map(channel => channel.connect().catch(error => {
console.error(`Failed to connect channel ${channel.id}:`, error);
return error;
}));
await Promise.allSettled(connections);
}
/**
* Disconnects all channels
*/
async disconnectAll() {
const disconnections = Array.from(this.channels.values()).map(channel => channel.disconnect().catch(error => {
console.error(`Failed to disconnect channel ${channel.id}:`, error);
return error;
}));
await Promise.allSettled(disconnections);
}
/**
* Sends a message through the best available channel
*/
async sendMessage(message, preferredChannelId) {
// Sign the message
const signedMessage = await message_protocol_1.MessageProtocol.signMessage(message, this.agentIdentity.keyPair);
// Create envelope
const envelope = message_protocol_1.MessageProtocol.createEnvelope(signedMessage, {
ttl: this.options.defaultMessageTTL
});
// Choose channel
const channel = this.selectChannel(message.to, preferredChannelId);
if (!channel) {
throw new Error(`No available channel for recipient: ${message.to}`);
}
try {
await this.sendThroughChannel(envelope, channel);
this.updateStats(channel.id, 'sent');
}
catch (error) {
this.updateStats(channel.id, 'error');
// Add to retry queue if retries are enabled
if (this.options.maxRetries > 0) {
this.pendingMessages.set(message.id, {
envelope,
retries: 0,
lastAttempt: new Date()
});
}
throw error;
}
}
/**
* Sends a delegation request to another agent
*/
async requestDelegation(targetAgentDID, requestedScopes, options = {}) {
const message = message_protocol_1.MessageProtocol.createMessage(types_1.AgentMessageType.DELEGATION_REQUEST, this.agentIdentity.did, targetAgentDID, {
requestedScopes,
serviceDID: options.serviceDID,
duration: options.duration,
purpose: options.purpose
});
await this.sendMessage(message, options.channelId);
}
/**
* Queries another agent's status
*/
async queryAgentStatus(targetAgentDID, options = {}) {
const message = message_protocol_1.MessageProtocol.createMessage(types_1.AgentMessageType.QUERY_STATUS, this.agentIdentity.did, targetAgentDID, {
includeChain: options.includeChain,
includeScopes: options.includeScopes,
includeMetrics: options.includeMetrics
});
await this.sendMessage(message, options.channelId);
}
/**
* Pings another agent
*/
async pingAgent(targetAgentDID, channelId) {
const message = message_protocol_1.MessageProtocol.createMessage(types_1.AgentMessageType.PING, this.agentIdentity.did, targetAgentDID, {});
await this.sendMessage(message, channelId);
}
/**
* Registers a custom message handler
*/
registerMessageHandler(type, handler) {
this.messageHandlers.registerHandler(type, handler);
}
/**
* Gets communication statistics
*/
getStats(channelId) {
if (channelId) {
return this.stats.get(channelId) || {
messagesSent: 0,
messagesReceived: 0,
messagesDropped: 0,
averageResponseTime: 0,
errorRate: 0
};
}
return this.stats;
}
/**
* Gets list of connected channels
*/
getConnectedChannels() {
return Array.from(this.channels.values())
.filter(channel => channel.isConnected)
.map(channel => channel.id);
}
/**
* Retries failed messages
*/
async retryFailedMessages() {
let retriedCount = 0;
const now = new Date();
for (const [messageId, pending] of this.pendingMessages.entries()) {
// Check if enough time has passed for retry
if (now.getTime() - pending.lastAttempt.getTime() >= this.options.retryDelay) {
if (pending.retries < this.options.maxRetries) {
try {
const channel = this.selectChannel(pending.envelope.message.to);
if (channel) {
await this.sendThroughChannel(pending.envelope, channel);
this.pendingMessages.delete(messageId);
retriedCount++;
}
else {
pending.retries++;
pending.lastAttempt = now;
}
}
catch (error) {
pending.retries++;
pending.lastAttempt = now;
if (pending.retries >= this.options.maxRetries) {
this.pendingMessages.delete(messageId);
}
}
}
else {
// Max retries reached, remove from queue
this.pendingMessages.delete(messageId);
}
}
}
return retriedCount;
}
// Private methods
async handleIncomingMessage(envelope, channelId) {
try {
// Validate and process envelope
const { message, errors } = await message_protocol_1.MessageProtocol.processEnvelope(envelope, this.agentIdentity.keyPair);
if (errors && errors.length > 0) {
console.error('Message processing errors:', errors);
this.updateStats(channelId, 'dropped');
return;
}
// Update stats
this.updateStats(channelId, 'received');
// Process message through handlers
const response = await this.messageHandlers.processMessage(message, this.agentIdentity, (responseMessage) => this.sendMessage(responseMessage, channelId));
// Send response if one was generated
if (response) {
await this.sendMessage(response, channelId);
}
}
catch (error) {
console.error('Error handling incoming message:', error);
this.updateStats(channelId, 'error');
}
}
selectChannel(recipientDID, preferredChannelId) {
// Try preferred channel first
if (preferredChannelId) {
const preferred = this.channels.get(preferredChannelId);
if (preferred && preferred.isConnected) {
return preferred;
}
}
// Find best available channel
const connectedChannels = Array.from(this.channels.values())
.filter(channel => channel.isConnected);
if (connectedChannels.length === 0) {
return null;
}
// Simple selection: prefer direct channels, then websockets
const direct = connectedChannels.find(ch => ch.type === 'direct');
if (direct)
return direct;
const websocket = connectedChannels.find(ch => ch.type === 'websocket');
if (websocket)
return websocket;
// Return first available
return connectedChannels[0];
}
async sendThroughChannel(envelope, channel) {
if (!channel.isConnected) {
throw new Error(`Channel ${channel.id} is not connected`);
}
await channel.send(envelope);
}
updateStats(channelId, type) {
if (!this.options.enableStats)
return;
const stats = this.stats.get(channelId);
if (!stats)
return;
switch (type) {
case 'sent':
stats.messagesSent++;
break;
case 'received':
stats.messagesReceived++;
stats.lastMessageTime = new Date();
break;
case 'dropped':
stats.messagesDropped++;
break;
case 'error':
const totalMessages = stats.messagesSent + stats.messagesReceived;
if (totalMessages > 0) {
stats.errorRate = (stats.messagesDropped + 1) / totalMessages;
}
break;
}
}
initializeStats() {
if (!this.options.enableStats)
return;
// Set up periodic retry of failed messages
setInterval(() => {
this.retryFailedMessages().catch(error => {
console.error('Error retrying failed messages:', error);
});
}, this.options.retryDelay);
}
}
exports.CommunicationManager = CommunicationManager;
//# sourceMappingURL=communication-manager.js.map