@nosana/kit
Version:
Nosana KIT
147 lines (146 loc) • 5.05 kB
JavaScript
import bs58 from 'bs58';
import axios, { AxiosHeaders } from 'axios';
// Import form-data dynamically for Node.js environments
let FormData;
let fs;
// Dynamically import Node.js-specific modules
const loadNodeModules = async () => {
if (typeof window === 'undefined') {
try {
const formDataModule = await import('form-data');
FormData = formDataModule.default;
fs = await import('fs');
}
catch (error) {
console.warn('Node.js modules not available for file operations');
}
}
else {
// Use browser FormData
FormData = window.FormData;
}
};
/**
* Class to interact with Pinata Cloud
* https://www.pinata.cloud/
*/
export class IPFS {
constructor(config) {
this.config = config;
const headers = new AxiosHeaders();
if (this.config.jwt) {
headers.set('Authorization', `Bearer ${this.config.jwt}`);
}
this.api = axios.create({
baseURL: this.config.api,
headers,
});
}
/**
* Convert the ipfs bytes from a solana job to a CID
* It prepends the 0x1220 (18,32) to make it 34 bytes and Base58 encodes it.
* This result is IPFS addressable.
*/
static solHashToIpfsHash(hashArray) {
let finalArray;
const inputArray = Array.isArray(hashArray) ? hashArray : Array.from(hashArray);
if (inputArray.length === 32) {
// Create a new array with the prepended bytes [18, 32] + original array
finalArray = new Uint8Array(34);
finalArray[0] = 18;
finalArray[1] = 32;
finalArray.set(inputArray, 2);
}
else {
// Use the array as-is if it's not 32 bytes
finalArray = new Uint8Array(inputArray);
}
const hash = bs58.encode(Buffer.from(finalArray));
if (hash === 'QmNLei78zWmzUdbeRB3CiUfAizWUrbeeZh5K1rhAQKCh51') {
return null;
}
return hash;
}
/**
* Converts IPFS hash to byte array needed to submit results
* @param hash IPFS hash
* @returns Array<number>
*/
static IpfsHashToByteArray(hash) {
if (hash.length === 34) {
return [...bs58.decode(hash).subarray(2)];
}
else {
return [...bs58.decode(hash)];
}
}
/**
* Retrieve data from IPFS using the configured gateway
* @param hash IPFS hash string or byte array
* @param options Additional axios request options
* @returns The retrieved data
*/
async retrieve(hash, options = {}) {
if (typeof hash !== 'string') {
const convertedHash = IPFS.solHashToIpfsHash(hash);
if (!convertedHash) {
throw new Error('Invalid hash provided');
}
hash = convertedHash;
}
const response = await axios.get(this.config.gateway + hash, options);
return response.data;
}
/**
* Function to pin data into Pinata Cloud
* @param data Object to pin into IPFS as JSON
* @returns The IPFS hash of the pinned data
*/
async pin(data) {
const response = await this.api.post('/pinning/pinJSONToIPFS', data);
return response.data.IpfsHash;
}
/**
* Function to pin a file into Pinata Cloud
* @param filePath Path to the file to pin
* @returns The IPFS hash of the pinned file
*/
async pinFile(filePath) {
// Ensure Node.js modules are loaded
await loadNodeModules();
if (!FormData || !fs) {
throw new Error('File operations are not supported in this environment');
}
const data = new FormData();
data.append('file', fs.createReadStream(filePath));
const response = await this.api.post('/pinning/pinFileToIPFS', data, {
headers: {
'Content-Type': `multipart/form-data; boundary=${data.getBoundary()}`,
Authorization: `Bearer ${this.config.jwt}`,
},
});
return response.data.IpfsHash;
}
/**
* Function to pin a file from buffer/blob into Pinata Cloud
* @param fileBuffer Buffer or Blob containing the file data
* @param fileName Name of the file
* @returns The IPFS hash of the pinned file
*/
async pinFileFromBuffer(fileBuffer, fileName) {
// Ensure FormData is available
await loadNodeModules();
if (!FormData) {
throw new Error('FormData is not available in this environment');
}
const data = new FormData();
data.append('file', fileBuffer, fileName);
const response = await this.api.post('/pinning/pinFileToIPFS', data, {
headers: {
'Content-Type': `multipart/form-data${data.getBoundary ? `; boundary=${data.getBoundary()}` : ''}`,
Authorization: `Bearer ${this.config.jwt}`,
},
});
return response.data.IpfsHash;
}
}