delegate-framework
Version:
A TypeScript framework for building robust, production-ready blockchain workflows with comprehensive error handling, logging, and testing. Maintained by delegate.fun
187 lines • 7.25 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PinataClient = void 0;
const error_handling_1 = require("../../../utils/error-handling");
class PinataClient {
constructor(config) {
this.requestId = 0;
if (!config.jwt) {
(0, error_handling_1.throwError)('JWT token is required for Pinata client', 'Pinata Config Error');
}
this.config = {
gateway: PinataClient.DEFAULT_GATEWAY,
timeout: PinataClient.DEFAULT_TIMEOUT,
retries: PinataClient.DEFAULT_RETRIES,
...config,
};
this.logger = this.config.logger;
}
/**
* Upload metadata to IPFS using Pinata
* @param metadata - The metadata object to upload
* @param fileName - Optional filename for the metadata
* @returns Promise<PinataUploadResult>
*/
async uploadMetadata(metadata, fileName = 'metadata.json') {
const requestId = ++this.requestId;
this.logger?.debug(`Request ${requestId} started: uploadMetadata`, { fileName });
try {
const metadataBlob = new Blob([JSON.stringify(metadata, null, 2)], {
type: 'application/json'
});
this.logger?.debug('Preparing metadata upload', {
fileName,
dataSize: metadataBlob.size,
});
return await this.makeRequest(async () => {
return await this.performUpload(metadataBlob, fileName, 'metadata');
}, 'uploadMetadata');
}
catch (error) {
this.logger?.error(`Request ${requestId} failed: uploadMetadata`, error);
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
/**
* Upload image to IPFS using Pinata
* @param imageBuffer - The image buffer to upload
* @param mimeType - The MIME type of the image (e.g., "image/png", "image/jpeg")
* @param fileName - Optional filename for the image
* @returns Promise<PinataUploadResult>
*/
async uploadImage(imageBuffer, mimeType, fileName = 'image') {
const requestId = ++this.requestId;
this.logger?.debug(`Request ${requestId} started: uploadImage`, {
fileName,
mimeType,
dataSize: imageBuffer.length,
});
try {
const imageBlob = new Blob([imageBuffer], { type: mimeType });
return await this.makeRequest(async () => {
return await this.performUpload(imageBlob, fileName, 'image');
}, 'uploadImage');
}
catch (error) {
this.logger?.error(`Request ${requestId} failed: uploadImage`, error);
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
/**
* Perform the actual upload with retry logic
* @param blob - Blob to upload
* @param fileName - Filename for the upload
* @param type - Type of upload for logging
* @returns Promise<PinataUploadResult>
*/
async performUpload(blob, fileName, type) {
try {
const formData = new FormData();
formData.append('file', blob, fileName);
formData.append('name', fileName);
formData.append('network', 'public');
this.logger?.debug(`Uploading ${type} to IPFS via Pinata`, {
fileName,
dataSize: blob.size,
});
const response = await fetch(PinataClient.UPLOAD_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.config.jwt}`,
},
body: formData,
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Pinata API error: ${response.status} - ${errorText}`);
}
const result = await response.json();
if (result && result.data && result.data.cid) {
const uri = `${this.config.gateway}/ipfs/${result.data.cid}`;
this.logger?.debug(`${type} upload completed`, {
cid: result.data.cid,
uri,
});
return {
success: true,
uri,
cid: result.data.cid,
};
}
else {
return {
success: false,
error: 'No CID returned from Pinata',
};
}
}
catch (error) {
this.logger?.error(`Error uploading ${type} to IPFS via Pinata`, error);
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
/**
* Make a request with retry logic and error handling
* @param operation - The operation to perform
* @param operationName - Name of the operation for logging
* @returns Result of the operation
*/
async makeRequest(operation, operationName) {
const requestId = ++this.requestId;
this.logger?.debug(`Request ${requestId} started: ${operationName}`);
let lastError;
for (let attempt = 1; attempt <= this.config.retries; attempt++) {
try {
const result = await Promise.race([
operation(),
new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Operation timed out after ${this.config.timeout}ms`));
}, this.config.timeout);
}),
]);
this.logger?.debug(`Request ${requestId} completed: ${operationName}`, { result });
return result;
}
catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
this.logger?.warn(`Request ${requestId} attempt ${attempt} failed: ${operationName}`, lastError);
if (attempt === this.config.retries) {
this.logger?.error(`Request ${requestId} failed after ${attempt} attempts: ${operationName}`, lastError);
throw lastError;
}
await this.delay(Math.pow(2, attempt - 1) * 1000);
}
}
throw lastError;
}
/**
* Utility method for delays
* @param ms - Milliseconds to delay
*/
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Get the current configuration
* @returns Current client configuration
*/
getConfig() {
return this.config;
}
}
exports.PinataClient = PinataClient;
PinataClient.DEFAULT_TIMEOUT = 60000; // 60 seconds for uploads
PinataClient.DEFAULT_RETRIES = 3;
PinataClient.DEFAULT_GATEWAY = 'https://gateway.pinata.cloud';
PinataClient.UPLOAD_URL = 'https://uploads.pinata.cloud/v3/files';
//# sourceMappingURL=pinata.js.map