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
JavaScript
"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