UNPKG

nucypher-experimental-taco-storage

Version:

TypeScript SDK for encrypted data storage with TACo (Threshold Access Control), supporting multiple storage providers including IPFS and SQLite

300 lines 10.8 kB
"use strict"; /** * Helia IPFS storage adapter implementation using embedded IPFS node */ Object.defineProperty(exports, "__esModule", { value: true }); exports.HeliaAdapter = void 0; const base_1 = require("./base"); const types_1 = require("../../types"); const helia_1 = require("helia"); const unixfs_1 = require("@helia/unixfs"); /** * Helia IPFS storage adapter for embedded IPFS node functionality */ class HeliaAdapter extends base_1.BaseIPFSAdapter { constructor(config = {}) { super(config); this.helia = null; this.fs = null; const heliaConfig = config; this.heliaConfig = { timeout: heliaConfig.timeout ?? 30000, // 30 second default timeout autoStart: heliaConfig.autoStart ?? true, heliaOptions: heliaConfig.heliaOptions }; } /** * Initialize the Helia adapter by creating and starting the embedded IPFS node */ async initialize() { try { // Create Helia node with optional custom configuration const heliaOptions = this.heliaConfig.heliaOptions || { // Default configuration for better compatibility libp2p: { addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] } } }; this.helia = await (0, helia_1.createHelia)(heliaOptions); // Create UnixFS interface for file operations this.fs = (0, unixfs_1.unixfs)(this.helia); // Start the node if autoStart is enabled if (this.heliaConfig.autoStart && this.helia.libp2p.status !== 'started') { await this.helia.libp2p.start(); } // Verify the node is operational const peerId = this.helia.libp2p.peerId; if (!peerId) { throw new Error('Failed to get peer ID from Helia node'); } } catch (error) { throw new types_1.TacoStorageError(types_1.TacoStorageErrorType.ADAPTER_ERROR, `Failed to initialize Helia IPFS adapter: ${error.message}`, error); } } generateReference(id) { return this.formatReference(id); } /** * Ensure Helia node and filesystem are initialized before use */ ensureHeliaReady() { if (!this.helia || !this.fs) { throw new types_1.TacoStorageError(types_1.TacoStorageErrorType.ADAPTER_ERROR, 'Helia IPFS adapter not initialized. Call initialize() first.'); } return { helia: this.helia, fs: this.fs }; } // Implementation of abstract methods from BaseIPFSAdapter async addContent(data) { const { fs } = this.ensureHeliaReady(); const cid = await fs.addBytes(data, { onProgress: (progress) => { // Optional: could emit progress events here } }); return cid.toString(); } async getContent(hash) { const { fs } = this.ensureHeliaReady(); const cid = this.parseCID(hash); const chunks = []; // Create timeout promise const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), this.heliaConfig.timeout)); try { const contentIterable = fs.cat(cid, { onProgress: (progress) => { // Optional: could emit progress events here } }); // Race between content retrieval and timeout const result = await Promise.race([ (async () => { for await (const chunk of contentIterable) { chunks.push(chunk); } return Buffer.concat(chunks); })(), timeoutPromise ]); return result; } catch (error) { if (error.message.includes('not found')) { throw new types_1.TacoStorageError(types_1.TacoStorageErrorType.NOT_FOUND, `Content not found for hash: ${hash}`, error); } throw error; } } async pinContent(hash) { const { helia } = this.ensureHeliaReady(); const cid = this.parseCID(hash); await helia.pins.add(cid); } async unpinContent(hash) { const { helia } = this.ensureHeliaReady(); const cid = this.parseCID(hash); await helia.pins.rm(cid); } async contentExists(hash) { const { helia } = this.ensureHeliaReady(); const cid = this.parseCID(hash); try { // Try to get the block to see if it exists return await helia.blockstore.has(cid); } catch { return false; } } async getIPFSHealth() { try { const { helia } = this.ensureHeliaReady(); const peerId = helia.libp2p.peerId; const isStarted = helia.libp2p.status === 'started'; return { connected: isStarted, nodeId: peerId.toString(), }; } catch { return { connected: false }; } } /** * Store encrypted data and metadata using Helia */ async store(encryptedData, metadata) { this.validateData(encryptedData); try { // Create a structured object containing both data and metadata const dataPackage = { data: Array.from(encryptedData), // Convert Uint8Array to regular array for JSON metadata: { ...metadata, createdAt: metadata.createdAt.toISOString(), encryptionMetadata: { ...metadata.encryptionMetadata, }, }, }; // Add to IPFS using Helia const ipfsHash = await this.addContent(new TextEncoder().encode(JSON.stringify(dataPackage))); // Pin the content by default await this.pinContent(ipfsHash); return { id: metadata.id, reference: this.generateReference(ipfsHash), metadata: this.createIPFSMetadata(ipfsHash, metadata), }; } catch (error) { throw new types_1.TacoStorageError(types_1.TacoStorageErrorType.STORAGE_ERROR, `Failed to store data using Helia: ${error.message}`, error); } } /** * Retrieve encrypted data and metadata using Helia */ async retrieve(id) { this.validateId(id); try { const ipfsHash = this.validateAndParseReference(id); // Retrieve from IPFS using Helia const content = await this.getContent(ipfsHash); const dataPackage = JSON.parse(new TextDecoder().decode(content)); // Reconstruct the original data and metadata const encryptedData = new Uint8Array(dataPackage.data); const metadata = { ...dataPackage.metadata, createdAt: new Date(dataPackage.metadata.createdAt), encryptionMetadata: { ...dataPackage.metadata.encryptionMetadata, encryptedKey: new Uint8Array(dataPackage.metadata.encryptionMetadata.encryptedKey), capsule: new Uint8Array(dataPackage.metadata.encryptionMetadata.capsule), }, }; return { encryptedData, metadata }; } catch (error) { if (error instanceof types_1.TacoStorageError && error.type === types_1.TacoStorageErrorType.NOT_FOUND) { throw error; } throw new types_1.TacoStorageError(types_1.TacoStorageErrorType.RETRIEVAL_ERROR, `Failed to retrieve data using Helia: ${error.message}`, error); } } /** * Delete data from IPFS (unpin) */ async delete(id) { this.validateId(id); const ipfsHash = this.parseReference(id); try { await this.unpinContent(ipfsHash); return true; } catch (error) { // If it's an initialization error, let it propagate if (error instanceof types_1.TacoStorageError && error.type === types_1.TacoStorageErrorType.ADAPTER_ERROR) { throw error; } // Return true even if unpin fails, as the content might not have been pinned return true; } } /** * Check if data exists in Helia */ async exists(id) { this.validateId(id); try { const ipfsHash = this.validateAndParseReference(id); return await this.contentExists(ipfsHash); } catch (error) { // If it's an initialization error, let it propagate if (error instanceof types_1.TacoStorageError && error.type === types_1.TacoStorageErrorType.ADAPTER_ERROR) { throw error; } return false; } } /** * Get Helia node health status */ async getHealth() { // Check if adapter is initialized if (!this.helia || !this.fs) { return { healthy: false, details: { status: 'not_initialized', error: 'Helia adapter not initialized' } }; } try { const { helia } = this.ensureHeliaReady(); const peerId = helia.libp2p.peerId; const isStarted = helia.libp2p.status === 'started'; const connections = helia.libp2p.getConnections(); return { healthy: isStarted, details: { nodeId: peerId.toString(), isStarted, connectionCount: connections.length, multiaddrs: helia.libp2p.getMultiaddrs().map(ma => ma.toString()), }, }; } catch (error) { return { healthy: false, details: { error: error.message, }, }; } } /** * Clean up Helia node resources */ async cleanup() { if (this.helia) { try { await this.helia.stop(); } catch (error) { // Log error but don't throw, cleanup should be non-blocking console.warn('Error stopping Helia node:', error); } finally { this.helia = null; this.fs = null; } } } } exports.HeliaAdapter = HeliaAdapter; //# sourceMappingURL=helia.js.map