anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
333 lines • 12 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.IPFSRedundancyManager = void 0;
const ipfs_activity_storage_1 = require("./ipfs-activity-storage");
class IPFSRedundancyManager {
constructor(config) {
this.storageNodes = new Map();
this.healthStatus = new Map();
this.config = {
retryAttempts: 3,
retryDelay: 1000,
...config
};
// Initialize storage nodes
this.initializeNodes();
}
initializeNodes() {
for (const node of this.config.nodes) {
if (node.active) {
const storage = new ipfs_activity_storage_1.IPFSActivityStorage({
url: node.url,
encryptionKey: this.config.encryptionKey
});
this.storageNodes.set(node.name, storage);
this.healthStatus.set(node.name, true);
}
}
}
/**
* Store activity across multiple nodes with redundancy
*/
async storeActivityWithRedundancy(activity) {
const results = [];
const activeNodes = this.getActiveNodes();
if (activeNodes.length < this.config.minReplicas) {
throw new Error(`Insufficient active nodes. Required: ${this.config.minReplicas}, Available: ${activeNodes.length}`);
}
// Store to all active nodes in parallel
const storePromises = activeNodes.map(async (node) => {
try {
const storage = this.storageNodes.get(node.name);
if (!storage) {
throw new Error(`Storage not found for node: ${node.name}`);
}
const result = await this.retryOperation(() => storage.storeActivity(activity), node.name);
return {
name: node.name,
ipfsHash: result.ipfsHash
};
}
catch (error) {
this.healthStatus.set(node.name, false);
return {
name: node.name,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
const storeResults = await Promise.all(storePromises);
// Check if minimum replicas were successful
const successfulNodes = storeResults.filter(r => r.ipfsHash).length;
return {
success: successfulNodes >= this.config.minReplicas,
nodes: storeResults
};
}
/**
* Store activity batch with redundancy
*/
async storeBatchWithRedundancy(batch) {
const results = [];
const activeNodes = this.getActiveNodes();
if (activeNodes.length < this.config.minReplicas) {
throw new Error(`Insufficient active nodes. Required: ${this.config.minReplicas}, Available: ${activeNodes.length}`);
}
const storePromises = activeNodes.map(async (node) => {
try {
const storage = this.storageNodes.get(node.name);
if (!storage) {
throw new Error(`Storage not found for node: ${node.name}`);
}
const result = await this.retryOperation(() => storage.storeActivityBatch(batch), node.name);
return {
name: node.name,
ipfsHash: result.ipfsHash
};
}
catch (error) {
this.healthStatus.set(node.name, false);
return {
name: node.name,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
const storeResults = await Promise.all(storePromises);
const successfulNodes = storeResults.filter(r => r.ipfsHash).length;
return {
success: successfulNodes >= this.config.minReplicas,
nodes: storeResults
};
}
/**
* Retrieve activity from any available node
*/
async retrieveActivity(ipfsHash) {
const activeNodes = this.getActiveNodes();
for (const node of activeNodes) {
try {
const storage = this.storageNodes.get(node.name);
if (!storage)
continue;
const activity = await storage.retrieveActivity(ipfsHash);
return activity;
}
catch (error) {
console.error(`Failed to retrieve from ${node.name}:`, error);
continue;
}
}
throw new Error(`Failed to retrieve activity from any node: ${ipfsHash}`);
}
/**
* Retrieve batch from any available node
*/
async retrieveBatch(ipfsHash) {
const activeNodes = this.getActiveNodes();
for (const node of activeNodes) {
try {
const storage = this.storageNodes.get(node.name);
if (!storage)
continue;
const batch = await storage.retrieveActivityBatch(ipfsHash);
return batch;
}
catch (error) {
console.error(`Failed to retrieve from ${node.name}:`, error);
continue;
}
}
throw new Error(`Failed to retrieve batch from any node: ${ipfsHash}`);
}
/**
* Pin content across minimum required nodes
*/
async pinWithRedundancy(ipfsHash) {
const activeNodes = this.getActiveNodes();
const results = [];
const pinPromises = activeNodes.slice(0, this.config.minReplicas).map(async (node) => {
try {
const storage = this.storageNodes.get(node.name);
if (!storage) {
throw new Error(`Storage not found for node: ${node.name}`);
}
await storage.pinActivity(ipfsHash);
return { name: node.name, pinned: true };
}
catch (error) {
return {
name: node.name,
pinned: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
});
const pinResults = await Promise.all(pinPromises);
const successfulPins = pinResults.filter(r => r.pinned).length;
return {
success: successfulPins >= this.config.minReplicas,
nodes: pinResults
};
}
/**
* Check health of all nodes
*/
async checkNodesHealth() {
const healthPromises = Array.from(this.storageNodes.entries()).map(async ([name, storage]) => {
try {
const connected = await storage.isConnected();
this.healthStatus.set(name, connected);
return { name, healthy: connected };
}
catch {
this.healthStatus.set(name, false);
return { name, healthy: false };
}
});
await Promise.all(healthPromises);
return new Map(this.healthStatus);
}
/**
* Get active nodes sorted by priority
*/
getActiveNodes() {
return this.config.nodes
.filter(node => node.active && this.healthStatus.get(node.name) !== false)
.sort((a, b) => b.priority - a.priority);
}
/**
* Retry operation with exponential backoff
*/
async retryOperation(operation, nodeName) {
let lastError = null;
for (let attempt = 0; attempt < this.config.retryAttempts; attempt++) {
try {
return await operation();
}
catch (error) {
lastError = error instanceof Error ? error : new Error('Unknown error');
if (attempt < this.config.retryAttempts - 1) {
const delay = this.config.retryDelay * Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError || new Error(`Failed after ${this.config.retryAttempts} attempts`);
}
/**
* Sync content between nodes
*/
async syncContent(ipfsHash, fromNode, toNodes) {
const synced = [];
const failed = [];
// First retrieve from source node
const sourceStorage = this.storageNodes.get(fromNode);
if (!sourceStorage) {
throw new Error(`Source node not found: ${fromNode}`);
}
let content = null;
try {
content = await sourceStorage.retrieveActivityBatch(ipfsHash);
}
catch {
// Try as single activity
try {
const activity = await sourceStorage.retrieveActivity(ipfsHash);
if (activity) {
content = {
id: ipfsHash,
activities: [activity],
startTime: activity.timestamp,
endTime: activity.timestamp,
count: 1,
agentDID: activity.agentDID,
parentDID: activity.parentDID
};
}
}
catch (error) {
throw new Error(`Failed to retrieve content from source: ${error}`);
}
}
if (!content) {
throw new Error('Content not found on source node');
}
// Sync to target nodes
const syncPromises = toNodes.map(async (nodeName) => {
try {
const targetStorage = this.storageNodes.get(nodeName);
if (!targetStorage) {
throw new Error(`Target node not found: ${nodeName}`);
}
await targetStorage.storeActivityBatch(content);
synced.push(nodeName);
}
catch (error) {
console.error(`Failed to sync to ${nodeName}:`, error);
failed.push(nodeName);
}
});
await Promise.all(syncPromises);
return {
success: synced.length > 0,
synced,
failed
};
}
/**
* Add a new IPFS node
*/
addNode(node) {
if (node.active) {
const storage = new ipfs_activity_storage_1.IPFSActivityStorage({
url: node.url,
encryptionKey: this.config.encryptionKey
});
this.storageNodes.set(node.name, storage);
this.healthStatus.set(node.name, true);
this.config.nodes.push(node);
}
}
/**
* Remove a node
*/
removeNode(nodeName) {
this.storageNodes.delete(nodeName);
this.healthStatus.delete(nodeName);
this.config.nodes = this.config.nodes.filter(n => n.name !== nodeName);
}
/**
* Get storage statistics across all nodes
*/
async getAggregateStats() {
const nodeStats = [];
let totalPinned = 0;
for (const [name, storage] of this.storageNodes.entries()) {
try {
const stats = await storage.getStorageStats();
nodeStats.push({
name,
pinned: stats.totalPinned,
healthy: this.healthStatus.get(name) || false
});
totalPinned += stats.totalPinned;
}
catch {
nodeStats.push({
name,
pinned: 0,
healthy: false
});
}
}
return {
totalNodes: this.config.nodes.length,
activeNodes: this.getActiveNodes().length,
totalPinned,
nodeStats
};
}
}
exports.IPFSRedundancyManager = IPFSRedundancyManager;
//# sourceMappingURL=ipfs-redundancy-manager.js.map