claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
299 lines • 9.91 kB
JavaScript
/**
* IPFS Client Module
* Low-level IPFS operations for discovery and fetching
*
* Supports multiple gateways with automatic fallback:
* - Pinata (recommended for pinned content)
* - Cloudflare IPFS
* - Protocol Labs ipfs.io
* - dweb.link (LibP2P)
*/
import * as crypto from 'crypto';
/**
* Available IPFS gateways in priority order
*/
export const IPFS_GATEWAYS = [
'https://gateway.pinata.cloud',
'https://cloudflare-ipfs.com',
'https://ipfs.io',
'https://dweb.link',
'https://w3s.link', // web3.storage gateway
];
/**
* IPNS resolvers
*/
export const IPNS_RESOLVERS = [
'https://gateway.pinata.cloud',
'https://dweb.link',
'https://ipfs.io',
];
/**
* Resolve IPNS name to CID with fallback across multiple gateways
*
* @param ipnsName - IPNS key or DNSLink domain
* @param preferredGateway - Optional preferred gateway to try first
* @returns CID string or null if resolution fails
*/
export async function resolveIPNS(ipnsName, preferredGateway) {
const resolvers = preferredGateway
? [preferredGateway, ...IPNS_RESOLVERS.filter(r => r !== preferredGateway)]
: IPNS_RESOLVERS;
console.log(`[IPFS] Resolving IPNS: ${ipnsName}`);
for (const gateway of resolvers) {
try {
const startTime = Date.now();
let cid = null;
// Method 1: DNSLink resolution for domain names
if (ipnsName.includes('.')) {
const response = await fetch(`${gateway}/api/v0/name/resolve?arg=/ipns/${ipnsName}`, {
signal: AbortSignal.timeout(10000),
headers: { 'Accept': 'application/json' },
});
if (response.ok) {
const data = await response.json();
cid = data.Path?.replace('/ipfs/', '') || null;
}
}
// Method 2: Direct IPNS key resolution via gateway redirect
if (!cid) {
const response = await fetch(`${gateway}/ipns/${ipnsName}`, {
method: 'HEAD',
signal: AbortSignal.timeout(10000),
redirect: 'follow',
});
if (response.ok) {
// Extract CID from the final URL after redirects
const finalUrl = response.url;
const cidMatch = finalUrl.match(/\/ipfs\/([a-zA-Z0-9]+)/);
if (cidMatch) {
cid = cidMatch[1];
}
}
}
if (cid) {
const latency = Date.now() - startTime;
console.log(`[IPFS] Resolved ${ipnsName} -> ${cid} via ${gateway} (${latency}ms)`);
return cid;
}
}
catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.warn(`[IPFS] Gateway ${gateway} failed: ${errorMsg}`);
continue;
}
}
console.warn(`[IPFS] IPNS resolution failed for ${ipnsName} on all gateways`);
return null;
}
/**
* Fetch content from IPFS by CID with fallback across multiple gateways
*
* @param cid - Content Identifier
* @param preferredGateway - Optional preferred gateway to try first
* @returns Parsed JSON content or null if fetch fails
*/
export async function fetchFromIPFS(cid, preferredGateway) {
const gateways = preferredGateway
? [preferredGateway, ...IPFS_GATEWAYS.filter(g => g !== preferredGateway)]
: IPFS_GATEWAYS;
console.log(`[IPFS] Fetching CID: ${cid}`);
for (const gateway of gateways) {
try {
const startTime = Date.now();
const url = `${gateway}/ipfs/${cid}`;
const response = await fetch(url, {
signal: AbortSignal.timeout(30000),
headers: {
'Accept': 'application/json',
'Cache-Control': 'max-age=3600',
},
});
if (response.ok) {
const data = await response.json();
const latency = Date.now() - startTime;
console.log(`[IPFS] Fetched ${cid} from ${gateway} (${latency}ms)`);
return data;
}
// Handle specific error codes
if (response.status === 504) {
console.warn(`[IPFS] Gateway timeout on ${gateway}, trying next...`);
}
else if (response.status === 429) {
console.warn(`[IPFS] Rate limited on ${gateway}, trying next...`);
}
}
catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.warn(`[IPFS] Gateway ${gateway} failed: ${errorMsg}`);
continue;
}
}
console.warn(`[IPFS] Fetch failed for ${cid} on all gateways`);
return null;
}
/**
* Fetch with full result metadata
*/
export async function fetchFromIPFSWithMetadata(cid, preferredGateway) {
const gateways = preferredGateway
? [preferredGateway, ...IPFS_GATEWAYS.filter(g => g !== preferredGateway)]
: IPFS_GATEWAYS;
for (const gateway of gateways) {
try {
const startTime = Date.now();
const url = `${gateway}/ipfs/${cid}`;
const response = await fetch(url, {
signal: AbortSignal.timeout(30000),
headers: {
'Accept': 'application/json',
},
});
if (response.ok) {
const data = await response.json();
const latencyMs = Date.now() - startTime;
const cached = response.headers.get('X-Cache')?.includes('HIT') ||
response.headers.get('CF-Cache-Status') === 'HIT';
return {
data,
gateway,
cid,
cached,
latencyMs,
};
}
}
catch {
continue;
}
}
return null;
}
/**
* Check if CID is pinned/available on a gateway
*/
export async function isPinned(cid, gateway = 'https://ipfs.io') {
try {
const response = await fetch(`${gateway}/ipfs/${cid}`, {
method: 'HEAD',
signal: AbortSignal.timeout(5000),
});
return response.ok;
}
catch {
return false;
}
}
/**
* Check availability across multiple gateways
*/
export async function checkAvailability(cid) {
const results = await Promise.all(IPFS_GATEWAYS.map(async (gateway) => {
const startTime = Date.now();
try {
const response = await fetch(`${gateway}/ipfs/${cid}`, {
method: 'HEAD',
signal: AbortSignal.timeout(5000),
});
return {
url: gateway,
available: response.ok,
latencyMs: Date.now() - startTime,
};
}
catch {
return {
url: gateway,
available: false,
latencyMs: Date.now() - startTime,
};
}
}));
return {
available: results.some(r => r.available),
gateways: results,
};
}
/**
* Get IPFS gateway URL for a CID
*/
export function getGatewayUrl(cid, gateway = 'https://ipfs.io') {
return `${gateway}/ipfs/${cid}`;
}
/**
* Get multiple gateway URLs for redundancy
*/
export function getGatewayUrls(cid) {
return IPFS_GATEWAYS.map(gateway => `${gateway}/ipfs/${cid}`);
}
/**
* Validate CID format (CIDv0 and CIDv1)
*/
export function isValidCID(cid) {
// CIDv0 starts with 'Qm' and is 46 characters (base58btc)
// CIDv1 starts with 'b' (base32) or 'z' (base58btc) or 'f' (base16)
return /^(Qm[1-9A-HJ-NP-Za-km-z]{44}|b[a-z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|f[0-9a-f]{50,})$/i.test(cid);
}
/**
* Validate IPNS name format
*/
export function isValidIPNS(ipnsName) {
// IPNS key format (k51...) or DNSLink domain
return /^(k51[a-z0-9]{59,}|[a-z0-9.-]+\.[a-z]{2,})$/i.test(ipnsName);
}
/**
* Generate content hash for verification
*/
export function hashContent(content) {
const buffer = typeof content === 'string' ? Buffer.from(content) : content;
return crypto.createHash('sha256').update(buffer).digest('hex');
}
/**
* Verify Ed25519 signature (async import to avoid bundling issues)
*/
export async function verifyEd25519Signature(message, signature, publicKey) {
try {
// Dynamic import to avoid bundling @noble/ed25519 if not used
const ed = await import('@noble/ed25519');
// Handle prefixed public key (e.g., "ed25519:abc123...")
const pubKeyHex = publicKey.replace(/^ed25519:/, '');
const isValid = await ed.verifyAsync(Buffer.from(signature, 'hex'), new TextEncoder().encode(message), Buffer.from(pubKeyHex, 'hex'));
return isValid;
}
catch (error) {
console.warn('[IPFS] Signature verification failed:', error);
return false;
}
}
/**
* Parse CID to extract metadata
*/
export function parseCID(cid) {
if (!isValidCID(cid)) {
return null;
}
if (cid.startsWith('Qm')) {
return {
version: 0,
codec: 'dag-pb',
hash: cid,
};
}
// CIDv1 - simplified parsing
return {
version: 1,
codec: 'dag-cbor', // Most common for JSON
hash: cid,
};
}
/**
* Format bytes to human readable
*/
export function formatBytes(bytes) {
if (bytes === 0)
return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
}
//# sourceMappingURL=client.js.map