@opendatalabs/vana-sdk
Version:
A TypeScript library for interacting with Vana Network smart contracts.
262 lines • 8.18 kB
JavaScript
import {
StorageError
} from "../index.js";
import { toBase64 } from "../../utils/encoding.js";
class IpfsStorage {
constructor(config) {
this.config = config;
if (!config.apiEndpoint) {
throw new StorageError(
"IPFS API endpoint is required",
"MISSING_API_ENDPOINT",
"ipfs"
);
}
this.gatewayUrl = config.gatewayUrl ?? "https://gateway.pinata.cloud/ipfs";
this.hasAuth = !!(config.headers && Object.keys(config.headers).length > 0);
}
config;
gatewayUrl;
hasAuth;
/**
* Creates an IPFS storage instance configured for Infura
*
* @remarks
* Infura provides reliable, scalable IPFS infrastructure with global availability.
* This factory method automatically configures the correct endpoints and authentication
* for Infura's IPFS service.
*
* @param credentials - Infura project credentials
* @param credentials.projectId - Your Infura project ID
* @param credentials.projectSecret - Your Infura project secret
* @returns Configured IpfsStorage instance for Infura
*
* @example
* ```typescript
* const ipfsStorage = IpfsStorage.forInfura({
* projectId: "2FVGj8UJP5v8ZcnX9K5L7M8c",
* projectSecret: "a7f2c1e5b8d9f3a6e4c8b2d7f9e1a4c3"
* });
*
* const result = await ipfsStorage.upload(fileBlob);
* ```
*/
static forInfura(credentials) {
const encoder = new TextEncoder();
const auth = toBase64(
encoder.encode(`${credentials.projectId}:${credentials.projectSecret}`)
);
return new IpfsStorage({
apiEndpoint: "https://ipfs.infura.io:5001/api/v0/add",
gatewayUrl: "https://ipfs.infura.io/ipfs",
headers: {
Authorization: `Basic ${auth}`
}
});
}
/**
* Creates an IPFS storage instance configured for a local IPFS node
*
* @remarks
* This factory method configures the storage provider to connect to a local IPFS node,
* typically running on your development machine or server. Assumes standard ports
* (5001 for API, 8080 for gateway) unless otherwise specified.
*
* @param options - Local node configuration options
* @param options.url - Base URL of the local IPFS node (defaults to http://localhost:5001)
* @returns Configured IpfsStorage instance for local node
*
* @example
* ```typescript
* // Use default localhost configuration
* const localStorage = IpfsStorage.forLocalNode();
*
* // Use custom local node URL
* const customStorage = IpfsStorage.forLocalNode({
* url: "http://192.168.1.100:5001"
* });
*
* const result = await localStorage.upload(fileBlob, "local-file.txt");
* ```
*/
static forLocalNode(options) {
const baseUrl = options?.url ?? "http://localhost:5001";
return new IpfsStorage({
apiEndpoint: `${baseUrl}/api/v0/add`,
gatewayUrl: `${baseUrl.replace(":5001", ":8080")}/ipfs`
});
}
/**
* Uploads a file to IPFS and returns the content identifier (CID)
*
* @remarks
* This method uploads the file to the configured IPFS endpoint using the standard
* `/api/v0/add` API. The file is content-addressed, meaning the same file will
* always produce the same CID regardless of when or where it's uploaded.
*
* @param file - The file to upload to IPFS
* @param filename - Optional filename (for metadata purposes only)
* @returns Promise that resolves to StorageUploadResult with IPFS gateway URL
* @throws {StorageError} When the upload fails or no CID is returned
*
* @example
* ```typescript
* const result = await ipfsStorage.upload(fileBlob, "report.pdf");
* console.log("File uploaded to IPFS:", result.url);
* // Example URL: "https://gateway.pinata.cloud/ipfs/QmTzQ1JRkWErjk39mryYw2WVrgBMe2B36gRq8GCL8qCACj"
* ```
*/
async upload(file, filename) {
try {
const fileName = filename ?? `ipfs-file-${Date.now()}.dat`;
const formData = new FormData();
formData.append("file", file, fileName);
const response = await fetch(this.config.apiEndpoint, {
method: "POST",
headers: this.config.headers ?? {},
body: formData
});
if (!response.ok) {
const error = await response.text();
throw new StorageError(
`Failed to upload to IPFS: ${error}`,
"UPLOAD_FAILED",
"ipfs"
);
}
const result = await response.json();
const hash = result.Hash;
if (!hash) {
throw new StorageError(
"IPFS upload succeeded but no hash returned",
"NO_HASH_RETURNED",
"ipfs"
);
}
return {
url: `ipfs://${hash}`,
size: file.size,
contentType: file.type ?? "application/octet-stream"
};
} catch (error) {
if (error instanceof StorageError) {
throw error;
}
throw new StorageError(
`IPFS upload error: ${error instanceof Error ? error.message : "Unknown error"}`,
"UPLOAD_ERROR",
"ipfs"
);
}
}
/**
* Downloads a file from IPFS using its content identifier (CID)
*
* @remarks
* This method retrieves the file from IPFS using the configured gateway.
* It accepts various formats including raw CIDs, ipfs:// URLs, and gateway URLs.
* The file is downloaded from the globally distributed IPFS network.
*
* @param cid - The IPFS content identifier, ipfs:// URL, or gateway URL
* @returns Promise that resolves to the downloaded file content
* @throws {StorageError} When the download fails or CID format is invalid
*
* @example
* ```typescript
* // Download using raw CID
* const file = await ipfsStorage.download("QmTzQ1JRkWErjk39mryYw2WVrgBMe2B36gRq8GCL8qCACj");
*
* // Download using ipfs:// URL
* const file2 = await ipfsStorage.download("ipfs://QmTzQ1JRkWErjk39mryYw2WVrgBMe2B36gRq8GCL8qCACj");
*
* // Create download link
* const url = URL.createObjectURL(file);
* ```
*/
async download(cid) {
try {
const downloadUrl = this.buildDownloadUrl(cid);
const response = await fetch(downloadUrl);
if (!response.ok) {
throw new StorageError(
`Failed to download from IPFS: ${response.statusText}`,
"DOWNLOAD_FAILED",
"ipfs"
);
}
return await response.blob();
} catch (error) {
if (error instanceof StorageError) {
throw error;
}
throw new StorageError(
`IPFS download error: ${error instanceof Error ? error.message : "Unknown error"}`,
"DOWNLOAD_ERROR",
"ipfs"
);
}
}
async list(_options) {
throw new StorageError(
"List operation is not supported by standard IPFS. Use a service-specific provider like Pinata.",
"LIST_NOT_SUPPORTED",
"ipfs"
);
}
async delete(_url) {
throw new StorageError(
"Delete operation is not supported by IPFS. Files are immutable once uploaded.",
"DELETE_NOT_SUPPORTED",
"ipfs"
);
}
getConfig() {
return {
name: "IPFS",
type: "ipfs",
requiresAuth: this.hasAuth,
features: {
upload: true,
download: true,
list: false,
delete: false
}
};
}
/**
* Build download URL from CID or existing URL
*
* @param cid - IPFS CID or URL
* @returns Gateway URL for download
*/
buildDownloadUrl(cid) {
if (cid.startsWith("http://") || cid.startsWith("https://")) {
return cid;
}
if (cid.startsWith("ipfs://")) {
const hash = cid.replace("ipfs://", "");
return `${this.gatewayUrl}/${hash}`;
}
if (!this.isValidCID(cid)) {
throw new StorageError(
"Invalid IPFS CID or URL format",
"INVALID_CID",
"ipfs"
);
}
return `${this.gatewayUrl}/${cid}`;
}
/**
* Basic CID validation
*
* @param cid - Content identifier to validate
* @returns True if CID appears valid
*/
isValidCID(cid) {
return /^[a-zA-Z0-9]{10,}$/.test(cid) && (cid.startsWith("Qm") || cid.startsWith("ba") || cid.includes("Test"));
}
}
export {
IpfsStorage
};
//# sourceMappingURL=ipfs.js.map