anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
440 lines • 14.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ActivityLogger = void 0;
exports.getActivityLogger = getActivityLogger;
exports.createActivity = createActivity;
const uuid_1 = require("uuid");
const ipfs_activity_storage_1 = require("./ipfs-activity-storage");
const ipfs_redundancy_manager_1 = require("./ipfs-redundancy-manager");
const activity_index_1 = require("./activity-index");
const activity_stream_manager_1 = require("./activity-stream-manager");
/**
* ActivityLogger - Core service for logging agent activities
*
* Provides activity tracking, batching, and real-time streaming
*/
class ActivityLogger {
constructor(config = {}) {
this.activityBuffer = [];
this.subscriptions = new Map();
this.hooks = new Map();
this.processingBatch = false;
this.config = {
batchSize: config.batchSize || 100,
batchInterval: config.batchInterval || 5000,
enableRealtime: config.enableRealtime ?? true,
enableBatching: config.enableBatching ?? true,
retentionDays: config.retentionDays || 90,
encryptionKey: config.encryptionKey,
enableIPFS: config.enableIPFS ?? false,
ipfsUrl: config.ipfsUrl,
enableRedundancy: config.enableRedundancy ?? false,
ipfsNodes: config.ipfsNodes,
minReplicas: config.minReplicas || 2,
enableIndexing: config.enableIndexing ?? true,
enableStreaming: config.enableStreaming ?? false
};
if (this.config.enableBatching) {
this.startBatchTimer();
}
// Initialize IPFS storage
if (this.config.enableIPFS) {
if (this.config.enableRedundancy && this.config.ipfsNodes) {
// Use redundancy manager for multiple nodes
this.redundancyManager = new ipfs_redundancy_manager_1.IPFSRedundancyManager({
nodes: this.config.ipfsNodes,
minReplicas: this.config.minReplicas || 2,
encryptionKey: this.config.encryptionKey
});
}
else {
// Use single node storage
this.ipfsStorage = new ipfs_activity_storage_1.IPFSActivityStorage({
url: this.config.ipfsUrl,
encryptionKey: this.config.encryptionKey
});
}
}
// Initialize activity index
if (this.config.enableIndexing) {
this.activityIndex = new activity_index_1.ActivityIndex();
}
// Initialize stream manager
if (this.config.enableStreaming) {
this.streamManager = new activity_stream_manager_1.ActivityStreamManager({
enableAlerts: true,
enableMetrics: true
});
}
}
/**
* Log a single activity
*/
async logActivity(activity) {
// Generate ID and timestamp if not provided
const fullActivity = {
id: activity.id || (0, uuid_1.v4)(),
timestamp: activity.timestamp || new Date(),
scopes: activity.scopes || [],
details: activity.details || {},
...activity
};
// Run before hooks
const shouldContinue = await this.runBeforeHooks(fullActivity);
if (!shouldContinue) {
throw new Error('Activity rejected by hook');
}
// Add to buffer if batching is enabled
if (this.config.enableBatching) {
this.activityBuffer.push(fullActivity);
// Check if we should process the batch immediately
if (this.activityBuffer.length >= this.config.batchSize) {
await this.processBatch();
}
}
// Notify real-time subscribers
if (this.config.enableRealtime) {
await this.notifySubscribers(fullActivity);
}
// Index the activity for searching
if (this.activityIndex) {
try {
await this.activityIndex.indexActivity(fullActivity);
}
catch (error) {
console.error('Failed to index activity:', error);
}
}
// Stream the activity for real-time updates
if (this.streamManager) {
try {
await this.streamManager.publishActivity(fullActivity);
}
catch (error) {
console.error('Failed to stream activity:', error);
}
}
// Run after hooks
await this.runAfterHooks(fullActivity);
return fullActivity;
}
/**
* Log multiple activities at once
*/
async logActivities(activities) {
const results = [];
for (const activity of activities) {
try {
const logged = await this.logActivity(activity);
results.push(logged);
}
catch (error) {
console.error('Failed to log activity:', error);
}
}
return results;
}
/**
* Subscribe to real-time activity updates
*/
subscribe(filter, callback) {
const subscriptionId = (0, uuid_1.v4)();
const subscription = {
id: subscriptionId,
agentDID: filter.agentDID,
parentDID: filter.parentDID,
types: filter.types,
callback,
unsubscribe: () => {
this.subscriptions.delete(subscriptionId);
}
};
this.subscriptions.set(subscriptionId, subscription);
return subscription;
}
/**
* Register a hook for activities
*/
registerHook(hook) {
const hooks = this.hooks.get(hook.type) || [];
hooks.push(hook);
this.hooks.set(hook.type, hooks);
}
/**
* Force process the current batch
*/
async flush() {
if (this.activityBuffer.length > 0) {
await this.processBatch();
}
}
/**
* Process a batch of activities
*/
async processBatch() {
if (this.processingBatch || this.activityBuffer.length === 0) {
return;
}
this.processingBatch = true;
const activities = [...this.activityBuffer];
this.activityBuffer = [];
try {
const batch = {
id: (0, uuid_1.v4)(),
activities,
startTime: activities[0].timestamp,
endTime: activities[activities.length - 1].timestamp,
count: activities.length,
agentDID: activities[0].agentDID,
parentDID: activities[0].parentDID
};
// Store to IPFS if enabled
if (this.config.enableIPFS) {
try {
if (this.redundancyManager) {
// Store with redundancy
const result = await this.redundancyManager.storeBatchWithRedundancy(batch);
if (result.success) {
const successfulNode = result.nodes.find(n => n.ipfsHash);
batch.batchHash = successfulNode?.ipfsHash;
console.log(`Stored batch with redundancy: ${result.nodes.filter(n => n.ipfsHash).length}/${result.nodes.length} nodes`);
}
else {
throw new Error('Failed to meet minimum replica requirement');
}
}
else if (this.ipfsStorage) {
// Store to single node
const storedBatch = await this.ipfsStorage.storeActivityBatch(batch);
console.log(`Stored activity batch to IPFS: ${storedBatch.ipfsHash}`);
batch.batchHash = storedBatch.ipfsHash;
}
}
catch (ipfsError) {
console.error('Failed to store batch to IPFS:', ipfsError);
// Continue processing even if IPFS storage fails
}
}
else {
console.log(`Processing activity batch: ${batch.count} activities`);
}
// Stream batch processed event
if (this.streamManager) {
try {
await this.streamManager.publishBatch(batch);
}
catch (error) {
console.error('Failed to stream batch:', error);
}
}
// Emit batch processed event
await this.emitBatchProcessed(batch);
}
catch (error) {
console.error('Failed to process batch:', error);
// Re-add activities to buffer for retry
this.activityBuffer.unshift(...activities);
}
finally {
this.processingBatch = false;
}
}
/**
* Start the batch processing timer
*/
startBatchTimer() {
this.batchTimer = setInterval(async () => {
await this.processBatch();
}, this.config.batchInterval);
}
/**
* Stop the batch processing timer
*/
stopBatchTimer() {
if (this.batchTimer) {
clearInterval(this.batchTimer);
this.batchTimer = undefined;
}
}
/**
* Notify subscribers of a new activity
*/
async notifySubscribers(activity) {
const promises = Array.from(this.subscriptions.values())
.filter(sub => this.matchesSubscription(activity, sub))
.map(sub => {
try {
return Promise.resolve(sub.callback(activity));
}
catch (error) {
console.error('Subscriber callback error:', error);
return Promise.resolve();
}
});
await Promise.all(promises);
}
/**
* Check if an activity matches a subscription filter
*/
matchesSubscription(activity, subscription) {
if (subscription.agentDID && activity.agentDID !== subscription.agentDID) {
return false;
}
if (subscription.parentDID && activity.parentDID !== subscription.parentDID) {
return false;
}
if (subscription.types && !subscription.types.includes(activity.type)) {
return false;
}
return true;
}
/**
* Run before hooks for an activity
*/
async runBeforeHooks(activity) {
const hooks = this.hooks.get(activity.type) || [];
for (const hook of hooks) {
if (hook.beforeActivity) {
const shouldContinue = await hook.beforeActivity(activity);
if (!shouldContinue) {
return false;
}
}
}
return true;
}
/**
* Run after hooks for an activity
*/
async runAfterHooks(activity) {
const hooks = this.hooks.get(activity.type) || [];
const promises = hooks
.filter(hook => hook.afterActivity)
.map(hook => hook.afterActivity(activity));
await Promise.all(promises);
}
/**
* Emit batch processed event
*/
async emitBatchProcessed(batch) {
// This will be extended in future phases
// For now, just log it
console.log(`Batch processed: ${batch.id}, Activities: ${batch.count}`);
}
/**
* Get current buffer size
*/
getBufferSize() {
return this.activityBuffer.length;
}
/**
* Get subscription count
*/
getSubscriptionCount() {
return this.subscriptions.size;
}
/**
* Retrieve activities from IPFS
*/
async retrieveActivitiesFromIPFS(ipfsHashes) {
if (!this.ipfsStorage) {
throw new Error('IPFS storage not enabled');
}
const activities = [];
for (const hash of ipfsHashes) {
try {
const batch = await this.ipfsStorage.retrieveActivityBatch(hash);
activities.push(...batch.activities);
}
catch (error) {
console.error(`Failed to retrieve batch ${hash}:`, error);
}
}
return activities;
}
/**
* Get IPFS storage instance
*/
getIPFSStorage() {
return this.ipfsStorage;
}
/**
* Get activity index instance
*/
getActivityIndex() {
return this.activityIndex;
}
/**
* Get stream manager instance
*/
getStreamManager() {
return this.streamManager;
}
/**
* Enable or disable IPFS storage
*/
async setIPFSEnabled(enabled, ipfsUrl) {
this.config.enableIPFS = enabled;
if (enabled && !this.ipfsStorage) {
this.ipfsStorage = new ipfs_activity_storage_1.IPFSActivityStorage({
url: ipfsUrl || this.config.ipfsUrl,
encryptionKey: this.config.encryptionKey
});
}
else if (!enabled && this.ipfsStorage) {
// Flush any pending activities before disabling
await this.flush();
this.ipfsStorage = undefined;
}
}
/**
* Check if IPFS is connected
*/
async checkIPFSConnection() {
if (!this.ipfsStorage) {
return false;
}
return await this.ipfsStorage.isConnected();
}
/**
* Cleanup resources
*/
async cleanup() {
// Stop batch timer
this.stopBatchTimer();
// Process remaining activities
await this.flush();
// Clear subscriptions
this.subscriptions.clear();
// Clear hooks
this.hooks.clear();
}
}
exports.ActivityLogger = ActivityLogger;
// Singleton instance for global access
let globalLogger = null;
/**
* Get or create the global activity logger instance
*/
function getActivityLogger(config) {
if (!globalLogger) {
globalLogger = new ActivityLogger(config);
}
return globalLogger;
}
/**
* Helper function to create a standard activity object
*/
function createActivity(type, params) {
return {
type,
agentDID: params.agentDID,
parentDID: params.parentDID,
serviceDID: params.serviceDID,
status: params.status,
scopes: params.scopes || [],
details: params.details || {},
sessionId: params.sessionId
};
}
//# sourceMappingURL=activity-logger.js.map