nucypher-experimental-taco-storage
Version:
TypeScript SDK for encrypted data storage with TACo (Threshold Access Control), supporting multiple storage providers including IPFS and SQLite
235 lines • 8.09 kB
JavaScript
"use strict";
/**
* Kubo IPFS storage adapter implementation using kubo-rpc-client
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.KuboAdapter = void 0;
const base_1 = require("./base");
const types_1 = require("../../types");
const kubo_rpc_client_1 = require("kubo-rpc-client");
/**
* Kubo IPFS storage adapter for connecting to external Kubo IPFS nodes via RPC
*/
class KuboAdapter extends base_1.BaseIPFSAdapter {
constructor(config = {}) {
super(config);
this.client = null;
const kuboConfig = config;
this.shouldPin = kuboConfig.pin ?? true;
this.timeout = kuboConfig.timeout ?? 30000; // 30 second default timeout
this.url = kuboConfig.url || 'http://localhost:5001';
// Client will be created in initialize() using static import
}
/**
* Initialize the Kubo adapter by testing connectivity and validating configuration
*/
async initialize() {
try {
// Create Kubo RPC client using static import
this.client = (0, kubo_rpc_client_1.create)({
url: this.url,
timeout: this.timeout,
});
// Test IPFS connectivity by getting node ID and version info
const [id, version] = await Promise.all([
this.client.id(),
this.client.version(),
]);
// Validate that we can communicate with the IPFS node
if (!id || !version) {
throw new Error('Invalid response from IPFS node');
}
}
catch (error) {
throw new types_1.TacoStorageError(types_1.TacoStorageErrorType.ADAPTER_ERROR, 'Failed to initialize Kubo IPFS adapter - check that IPFS node is running and accessible', error);
}
}
generateReference(id) {
return this.formatReference(id);
}
/**
* Ensure client is initialized before use
*/
ensureClient() {
if (!this.client) {
throw new types_1.TacoStorageError(types_1.TacoStorageErrorType.ADAPTER_ERROR, 'Kubo IPFS adapter not initialized. Call initialize() first.');
}
return this.client;
}
// Implementation of abstract methods from BaseIPFSAdapter
async addContent(data) {
const client = this.ensureClient();
const result = await client.add(data, {
pin: this.shouldPin,
cidVersion: 1,
});
return result.cid.toString();
}
async getContent(hash) {
const client = this.ensureClient();
const stream = client.cat(hash, { timeout: this.timeout });
const chunks = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
return Buffer.concat(chunks);
}
async pinContent(hash) {
if (this.shouldPin) {
const client = this.ensureClient();
await client.pin.add(hash);
}
}
async unpinContent(hash) {
if (this.shouldPin) {
const client = this.ensureClient();
await client.pin.rm(hash);
}
}
async contentExists(hash) {
try {
const client = this.ensureClient();
await client.files.stat(`/ipfs/${hash}`, { timeout: 5000 });
return true;
}
catch {
return false;
}
}
async getIPFSHealth() {
try {
const client = this.ensureClient();
const nodeInfo = await client.id();
return {
connected: true,
nodeId: nodeInfo.id.toString(),
};
}
catch {
return { connected: false };
}
}
/**
* Store encrypted data and metadata on IPFS
*/
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
const ipfsHash = await this.addContent(new TextEncoder().encode(JSON.stringify(dataPackage)));
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 on IPFS: ${error.message}`, error);
}
}
/**
* Retrieve encrypted data and metadata from IPFS
*/
async retrieve(id) {
this.validateId(id);
try {
const ipfsHash = this.validateAndParseReference(id);
// Retrieve from IPFS
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.code === 'ERR_NOT_FOUND') {
throw new types_1.TacoStorageError(types_1.TacoStorageErrorType.NOT_FOUND, `Data not found for ID: ${id}`, error);
}
throw new types_1.TacoStorageError(types_1.TacoStorageErrorType.RETRIEVAL_ERROR, `Failed to retrieve data from IPFS: ${error.message}`, error);
}
}
/**
* Delete data from IPFS (unpin if pinned)
*/
async delete(id) {
this.validateId(id);
try {
const ipfsHash = this.parseReference(id);
await this.unpinContent(ipfsHash);
return true;
}
catch (error) {
// IPFS doesn't really "delete" content, just unpins it
// Return true even if unpin fails, as the content might not have been pinned
return true;
}
}
/**
* Check if data exists on IPFS
*/
async exists(id) {
this.validateId(id);
try {
const ipfsHash = this.validateAndParseReference(id);
return await this.contentExists(ipfsHash);
}
catch {
return false;
}
}
/**
* Get IPFS node health status
*/
async getHealth() {
try {
const client = this.ensureClient();
const nodeId = await client.id();
const version = await client.version();
return {
healthy: true,
details: {
nodeId: nodeId.id,
version: version.version,
addresses: nodeId.addresses,
},
};
}
catch (error) {
return {
healthy: false,
details: {
error: error.message,
},
};
}
}
/**
* Clean up IPFS client resources
*/
async cleanup() {
// IPFS HTTP client doesn't require explicit cleanup
// This method is here for interface compliance
}
}
exports.KuboAdapter = KuboAdapter;
//# sourceMappingURL=kubo.js.map