anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
438 lines • 17.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ActivityStreamManager = exports.AlertSeverity = exports.AlertType = exports.StreamEventType = void 0;
const events_1 = require("events");
const types_1 = require("./types");
var StreamEventType;
(function (StreamEventType) {
StreamEventType["ACTIVITY_LOGGED"] = "activity_logged";
StreamEventType["BATCH_PROCESSED"] = "batch_processed";
StreamEventType["ACTIVITY_ALERT"] = "activity_alert";
StreamEventType["AGENT_SESSION_START"] = "agent_session_start";
StreamEventType["AGENT_SESSION_END"] = "agent_session_end";
StreamEventType["SYSTEM_ALERT"] = "system_alert";
})(StreamEventType || (exports.StreamEventType = StreamEventType = {}));
var AlertType;
(function (AlertType) {
AlertType["HIGH_ERROR_RATE"] = "high_error_rate";
AlertType["SUSPICIOUS_ACTIVITY"] = "suspicious_activity";
AlertType["PERMISSION_ESCALATION"] = "permission_escalation";
AlertType["UNUSUAL_PATTERN"] = "unusual_pattern";
AlertType["RATE_LIMIT_EXCEEDED"] = "rate_limit_exceeded";
AlertType["SYSTEM_ERROR"] = "system_error";
})(AlertType || (exports.AlertType = AlertType = {}));
var AlertSeverity;
(function (AlertSeverity) {
AlertSeverity["LOW"] = "low";
AlertSeverity["MEDIUM"] = "medium";
AlertSeverity["HIGH"] = "high";
AlertSeverity["CRITICAL"] = "critical";
})(AlertSeverity || (exports.AlertSeverity = AlertSeverity = {}));
class ActivityStreamManager extends events_1.EventEmitter {
constructor(config = {}) {
super();
this.subscriptions = new Map();
this.recentEvents = [];
this.config = {
maxSubscriptions: config.maxSubscriptions || 1000,
eventRetentionMs: config.eventRetentionMs || 60 * 60 * 1000, // 1 hour
alertThresholds: {
errorRateThreshold: 0.2, // 20%
suspiciousVolumeThreshold: 100, // 100 activities per minute
unusualHoursThreshold: 50, // 50 activities outside business hours
...config.alertThresholds
},
enableAlerts: config.enableAlerts ?? true,
enableMetrics: config.enableMetrics ?? true
};
this.metrics = {
totalEvents: 0,
totalSubscriptions: 0,
eventsByType: {},
alertsGenerated: 0
};
// Initialize event type counters
Object.values(StreamEventType).forEach(type => {
this.metrics.eventsByType[type] = 0;
});
// Start cleanup timer
this.startCleanupTimer();
}
/**
* Subscribe to activity stream with filters
*/
subscribe(filters, callback, metadata) {
if (this.subscriptions.size >= this.config.maxSubscriptions) {
throw new Error('Maximum subscription limit reached');
}
const subscriptionId = this.generateId('sub');
const subscription = {
id: subscriptionId,
filters,
callback,
metadata,
createdAt: new Date(),
unsubscribe: () => {
this.subscriptions.delete(subscriptionId);
this.metrics.totalSubscriptions--;
}
};
this.subscriptions.set(subscriptionId, subscription);
this.metrics.totalSubscriptions++;
return subscription;
}
/**
* Subscribe to specific agent activities
*/
subscribeToAgent(agentDID, callback, metadata) {
return this.subscribe({ agentDID }, callback, metadata);
}
/**
* Subscribe to user activities (all agents under a parent DID)
*/
subscribeToUser(parentDID, callback, metadata) {
return this.subscribe({ parentDID }, callback, metadata);
}
/**
* Subscribe to critical events only
*/
subscribeToCriticalEvents(callback, metadata) {
return this.subscribe({
critical: true,
status: [types_1.ActivityStatus.FAILED, types_1.ActivityStatus.DENIED]
}, callback, metadata);
}
/**
* Subscribe to alerts
*/
subscribeToAlerts(callback, severity, metadata) {
const filters = {};
if (severity) {
filters.critical = severity.includes(AlertSeverity.CRITICAL);
}
const subscription = this.subscribe(filters, callback, metadata);
// Also listen for alert events specifically
const alertCallback = (event) => {
if (event.type === StreamEventType.ACTIVITY_ALERT) {
const alert = event.data;
if (!severity || severity.includes(alert.severity)) {
callback(event);
}
}
};
// Override callback to handle alerts
subscription.callback = (event) => {
if (event.type === StreamEventType.ACTIVITY_ALERT) {
alertCallback(event);
}
else {
callback(event);
}
};
return subscription;
}
/**
* Publish an activity to subscribers
*/
async publishActivity(activity) {
const event = {
id: this.generateId('evt'),
timestamp: new Date(),
type: StreamEventType.ACTIVITY_LOGGED,
data: activity
};
await this.publishEvent(event);
// Check for alerts if enabled
if (this.config.enableAlerts) {
await this.checkForAlerts(activity);
}
}
/**
* Publish a batch processed event
*/
async publishBatch(batch) {
const event = {
id: this.generateId('evt'),
timestamp: new Date(),
type: StreamEventType.BATCH_PROCESSED,
data: batch,
metadata: {
activityCount: batch.count,
batchSize: batch.activities.length
}
};
await this.publishEvent(event);
}
/**
* Publish an alert
*/
async publishAlert(alert) {
const event = {
id: this.generateId('evt'),
timestamp: new Date(),
type: StreamEventType.ACTIVITY_ALERT,
data: alert,
metadata: {
severity: alert.severity,
alertType: alert.alertType
}
};
await this.publishEvent(event);
this.metrics.alertsGenerated++;
}
/**
* Get recent events (for replay/catch-up)
*/
getRecentEvents(filters, limit = 100) {
let events = [...this.recentEvents];
// Apply filters if provided
if (filters) {
events = events.filter(event => this.matchesFilter(event, filters));
}
return events
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
.slice(0, limit);
}
/**
* Get subscription statistics
*/
getSubscriptionStats() {
const byFilter = {};
let active = 0;
this.subscriptions.forEach(sub => {
const now = new Date();
const timeSinceActivity = sub.lastActivity ?
now.getTime() - sub.lastActivity.getTime() :
now.getTime() - sub.createdAt.getTime();
if (timeSinceActivity < 5 * 60 * 1000) { // Active in last 5 minutes
active++;
}
// Categorize by main filter type
if (sub.filters.agentDID) {
byFilter['agent'] = (byFilter['agent'] || 0) + 1;
}
else if (sub.filters.parentDID) {
byFilter['user'] = (byFilter['user'] || 0) + 1;
}
else if (sub.filters.critical) {
byFilter['critical'] = (byFilter['critical'] || 0) + 1;
}
else {
byFilter['general'] = (byFilter['general'] || 0) + 1;
}
});
return {
total: this.subscriptions.size,
byFilter,
active
};
}
/**
* Get streaming metrics
*/
getMetrics() {
return { ...this.metrics };
}
/**
* Clear old events and inactive subscriptions
*/
cleanup() {
const now = new Date();
const cutoff = now.getTime() - this.config.eventRetentionMs;
// Remove old events
this.recentEvents = this.recentEvents.filter(event => event.timestamp.getTime() > cutoff);
// Remove inactive subscriptions (no activity for 1 hour)
const inactivityThreshold = 60 * 60 * 1000; // 1 hour
const toRemove = [];
this.subscriptions.forEach((sub, id) => {
const timeSinceActivity = sub.lastActivity ?
now.getTime() - sub.lastActivity.getTime() :
now.getTime() - sub.createdAt.getTime();
if (timeSinceActivity > inactivityThreshold) {
toRemove.push(id);
}
});
toRemove.forEach(id => {
this.subscriptions.delete(id);
this.metrics.totalSubscriptions--;
});
}
// Private methods
async publishEvent(event) {
// Store event for replay
this.recentEvents.push(event);
// Update metrics
this.metrics.totalEvents++;
this.metrics.eventsByType[event.type]++;
// Notify matching subscriptions
const matchingSubscriptions = Array.from(this.subscriptions.values())
.filter(sub => this.matchesFilter(event, sub.filters));
const promises = matchingSubscriptions.map(async (sub) => {
try {
sub.lastActivity = new Date();
await Promise.resolve(sub.callback(event));
}
catch (error) {
console.error('Subscription callback error:', error);
}
});
await Promise.all(promises);
// Emit to EventEmitter listeners
this.emit('event', event);
this.emit(event.type, event);
}
matchesFilter(event, filters) {
const activity = event.data;
// Only process activity events for most filters
if (event.type !== StreamEventType.ACTIVITY_LOGGED &&
event.type !== StreamEventType.ACTIVITY_ALERT) {
return true; // Allow all non-activity events through
}
if (filters.agentDID) {
const agentDIDs = Array.isArray(filters.agentDID) ? filters.agentDID : [filters.agentDID];
if (!agentDIDs.includes(activity.agentDID)) {
return false;
}
}
if (filters.parentDID) {
const parentDIDs = Array.isArray(filters.parentDID) ? filters.parentDID : [filters.parentDID];
if (!parentDIDs.includes(activity.parentDID)) {
return false;
}
}
if (filters.serviceDID) {
const serviceDIDs = Array.isArray(filters.serviceDID) ? filters.serviceDID : [filters.serviceDID];
if (!serviceDIDs.includes(activity.serviceDID)) {
return false;
}
}
if (filters.types && !filters.types.includes(activity.type)) {
return false;
}
if (filters.status && !filters.status.includes(activity.status)) {
return false;
}
if (filters.scopes) {
const hasScope = filters.scopes.some(scope => activity.scopes.includes(scope));
if (!hasScope) {
return false;
}
}
if (filters.critical) {
// Critical events: failures, high duration, or specific types
const isCritical = activity.status === types_1.ActivityStatus.FAILED ||
activity.status === types_1.ActivityStatus.DENIED ||
(activity.duration && activity.duration > 5000) ||
activity.type === types_1.ActivityType.ERROR;
if (!isCritical) {
return false;
}
}
if (filters.minDuration && activity.duration && activity.duration < filters.minDuration) {
return false;
}
if (filters.errorOnly) {
const isError = activity.status === types_1.ActivityStatus.FAILED ||
activity.status === types_1.ActivityStatus.DENIED;
if (!isError) {
return false;
}
}
return true;
}
async checkForAlerts(activity) {
// Check for high error rate
if (activity.status === types_1.ActivityStatus.FAILED || activity.status === types_1.ActivityStatus.DENIED) {
const recentActivities = this.getRecentActivityCount(activity.agentDID, 5 * 60 * 1000); // 5 minutes
const recentErrors = this.getRecentErrorCount(activity.agentDID, 5 * 60 * 1000);
if (recentActivities > 10 && recentErrors / recentActivities > this.config.alertThresholds.errorRateThreshold) {
await this.publishAlert({
id: this.generateId('alert'),
agentDID: activity.agentDID,
parentDID: activity.parentDID,
alertType: AlertType.HIGH_ERROR_RATE,
severity: AlertSeverity.HIGH,
message: `High error rate detected: ${Math.round(recentErrors / recentActivities * 100)}% (${recentErrors}/${recentActivities})`,
timestamp: new Date(),
relatedActivityId: activity.id,
details: {
errorRate: recentErrors / recentActivities,
totalActivities: recentActivities,
totalErrors: recentErrors
}
});
}
}
// Check for suspicious volume
const recentCount = this.getRecentActivityCount(activity.agentDID, 60 * 1000); // 1 minute
if (recentCount > this.config.alertThresholds.suspiciousVolumeThreshold) {
await this.publishAlert({
id: this.generateId('alert'),
agentDID: activity.agentDID,
parentDID: activity.parentDID,
alertType: AlertType.SUSPICIOUS_ACTIVITY,
severity: AlertSeverity.MEDIUM,
message: `Suspicious activity volume: ${recentCount} activities in the last minute`,
timestamp: new Date(),
relatedActivityId: activity.id,
details: {
activityCount: recentCount,
timeWindow: '1 minute'
}
});
}
// Check for unusual hours (outside 8 AM - 6 PM)
const hour = activity.timestamp.getHours();
if (hour < 8 || hour > 18) {
const unusualHoursCount = this.getRecentUnusualHoursCount(activity.agentDID, 24 * 60 * 60 * 1000); // 24 hours
if (unusualHoursCount > this.config.alertThresholds.unusualHoursThreshold) {
await this.publishAlert({
id: this.generateId('alert'),
agentDID: activity.agentDID,
parentDID: activity.parentDID,
alertType: AlertType.UNUSUAL_PATTERN,
severity: AlertSeverity.LOW,
message: `Unusual activity pattern: ${unusualHoursCount} activities outside business hours in the last 24 hours`,
timestamp: new Date(),
relatedActivityId: activity.id,
details: {
unusualHoursCount,
currentHour: hour,
timeWindow: '24 hours'
}
});
}
}
}
getRecentActivityCount(agentDID, timeWindowMs) {
const cutoff = Date.now() - timeWindowMs;
return this.recentEvents.filter(event => event.type === StreamEventType.ACTIVITY_LOGGED &&
event.timestamp.getTime() > cutoff &&
event.data.agentDID === agentDID).length;
}
getRecentErrorCount(agentDID, timeWindowMs) {
const cutoff = Date.now() - timeWindowMs;
return this.recentEvents.filter(event => event.type === StreamEventType.ACTIVITY_LOGGED &&
event.timestamp.getTime() > cutoff &&
event.data.agentDID === agentDID &&
[types_1.ActivityStatus.FAILED, types_1.ActivityStatus.DENIED].includes(event.data.status)).length;
}
getRecentUnusualHoursCount(agentDID, timeWindowMs) {
const cutoff = Date.now() - timeWindowMs;
return this.recentEvents.filter(event => event.type === StreamEventType.ACTIVITY_LOGGED &&
event.timestamp.getTime() > cutoff &&
event.data.agentDID === agentDID &&
(event.timestamp.getHours() < 8 || event.timestamp.getHours() > 18)).length;
}
generateId(prefix) {
return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
startCleanupTimer() {
// Run cleanup every 5 minutes
setInterval(() => {
this.cleanup();
}, 5 * 60 * 1000);
}
}
exports.ActivityStreamManager = ActivityStreamManager;
//# sourceMappingURL=activity-stream-manager.js.map