122 lines
3.5 KiB
JavaScript
122 lines
3.5 KiB
JavaScript
const NodeCache = require('node-cache');
|
|
const config = require('../config');
|
|
const { IPFS_GATEWAY, CACHE_ENABLED, CACHE_TTL_SECONDS } = config;
|
|
|
|
// Setup cache
|
|
const cache = new NodeCache({
|
|
stdTTL: CACHE_TTL_SECONDS,
|
|
checkperiod: CACHE_TTL_SECONDS * 0.2,
|
|
});
|
|
|
|
// MIME type mapping helper
|
|
const mimeTypes = {
|
|
'.html': 'text/html',
|
|
'.css': 'text/css',
|
|
'.js': 'application/javascript',
|
|
'.json': 'application/json',
|
|
'.png': 'image/png',
|
|
'.jpg': 'image/jpeg',
|
|
'.jpeg': 'image/jpeg',
|
|
'.gif': 'image/gif',
|
|
'.svg': 'image/svg+xml',
|
|
'.pdf': 'application/pdf',
|
|
'.txt': 'text/plain'
|
|
};
|
|
|
|
/**
|
|
* Fetch content from IPFS by CID and path, or from IPNS
|
|
* @param {string} cidOrIpns - IPFS Content Identifier or IPNS hash (prefixed with 'ipns:')
|
|
* @param {string} path - Optional path within the CID/IPNS
|
|
* @returns {Promise<{data: Buffer, mimeType: string}|null>} - Content and MIME type or null
|
|
*/
|
|
async function fetchFromIpfs(cidOrIpns, path = '') {
|
|
const isIpns = cidOrIpns.startsWith('ipns:');
|
|
const hash = isIpns ? cidOrIpns.substring(5) : cidOrIpns;
|
|
const contentType = isIpns ? 'ipns' : 'ipfs';
|
|
const contentPath = path ? `${hash}/${path}` : hash;
|
|
|
|
// Check cache first
|
|
if (CACHE_ENABLED) {
|
|
const cacheKey = `${contentType}:${contentPath}`;
|
|
const cachedContent = cache.get(cacheKey);
|
|
if (cachedContent) {
|
|
console.log(`Cache hit for ${contentType.toUpperCase()} content: ${contentPath}`);
|
|
return cachedContent;
|
|
}
|
|
}
|
|
|
|
try {
|
|
// Use the HTTP gateway directly
|
|
const result = await fetchViaGateway(hash, path, contentType);
|
|
|
|
if (!result) {
|
|
return null;
|
|
}
|
|
|
|
// Determine MIME type if not set
|
|
if (!result.mimeType) {
|
|
result.mimeType = getMimeType(path);
|
|
}
|
|
|
|
// Cache the result
|
|
if (CACHE_ENABLED) {
|
|
const cacheKey = `${contentType}:${contentPath}`;
|
|
// Use shorter TTL for IPNS content since it can change
|
|
const ttl = isIpns ? Math.min(CACHE_TTL_SECONDS, 300) : CACHE_TTL_SECONDS;
|
|
cache.set(cacheKey, result, ttl);
|
|
}
|
|
|
|
return result;
|
|
} catch (error) {
|
|
console.error(`Error fetching ${contentPath} from ${contentType.toUpperCase()}:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch content via IPFS HTTP gateway
|
|
* @param {string} hash - IPFS CID or IPNS hash
|
|
* @param {string} path - Path within the content
|
|
* @param {string} contentType - Either 'ipfs' or 'ipns'
|
|
* @returns {Promise<{data: Buffer, mimeType: string}|null>} - Content and MIME type or null
|
|
*/
|
|
async function fetchViaGateway(hash, path, contentType = 'ipfs') {
|
|
try {
|
|
const url = new URL(`${IPFS_GATEWAY}/${contentType}/${hash}${path ? '/' + path : ''}`);
|
|
console.log(`Fetching from IPFS gateway: ${url}`);
|
|
|
|
const response = await fetch(url);
|
|
|
|
if (!response.ok) {
|
|
console.error(`Gateway returned ${response.status} for ${url}`);
|
|
return null;
|
|
}
|
|
|
|
const data = Buffer.from(await response.arrayBuffer());
|
|
const mimeType = response.headers.get('content-type');
|
|
|
|
return { data, mimeType };
|
|
} catch (error) {
|
|
console.error('Gateway fetch error:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine MIME type from file path
|
|
* @param {string} path - File path
|
|
* @returns {string} - MIME type or default
|
|
*/
|
|
function getMimeType(path) {
|
|
if (!path) return 'application/octet-stream';
|
|
|
|
const extension = path.split('.').pop();
|
|
if (!extension) return 'application/octet-stream';
|
|
|
|
return mimeTypes['.' + extension] || 'application/octet-stream';
|
|
}
|
|
|
|
module.exports = {
|
|
fetchFromIpfs
|
|
};
|